waldiez 0.4.7__py3-none-any.whl → 0.4.8__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.

Files changed (244) hide show
  1. waldiez/__init__.py +5 -5
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +112 -73
  4. waldiez/exporter.py +61 -19
  5. waldiez/exporting/__init__.py +25 -6
  6. waldiez/exporting/agent/__init__.py +7 -3
  7. waldiez/exporting/agent/code_execution.py +114 -0
  8. waldiez/exporting/agent/exporter.py +354 -0
  9. waldiez/exporting/agent/extras/__init__.py +15 -0
  10. waldiez/exporting/agent/extras/captain_agent_extras.py +315 -0
  11. waldiez/exporting/agent/extras/group/target.py +178 -0
  12. waldiez/exporting/agent/extras/group_manager_agent_extas.py +500 -0
  13. waldiez/exporting/agent/extras/group_member_extras.py +181 -0
  14. waldiez/exporting/agent/extras/handoffs/__init__.py +19 -0
  15. waldiez/exporting/agent/extras/handoffs/after_work.py +78 -0
  16. waldiez/exporting/agent/extras/handoffs/available.py +74 -0
  17. waldiez/exporting/agent/extras/handoffs/condition.py +158 -0
  18. waldiez/exporting/agent/extras/handoffs/handoff.py +171 -0
  19. waldiez/exporting/agent/extras/handoffs/target.py +189 -0
  20. waldiez/exporting/agent/extras/rag/__init__.py +10 -0
  21. waldiez/exporting/agent/{utils/rag_user/chroma_utils.py → extras/rag/chroma_extras.py} +16 -15
  22. waldiez/exporting/agent/{utils/rag_user/mongo_utils.py → extras/rag/mongo_extras.py} +10 -10
  23. waldiez/exporting/agent/{utils/rag_user/pgvector_utils.py → extras/rag/pgvector_extras.py} +13 -13
  24. waldiez/exporting/agent/{utils/rag_user/qdrant_utils.py → extras/rag/qdrant_extras.py} +13 -13
  25. waldiez/exporting/agent/{utils/rag_user/vector_db.py → extras/rag/vector_db_extras.py} +59 -46
  26. waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +245 -0
  27. waldiez/exporting/agent/extras/reasoning_agent_extras.py +88 -0
  28. waldiez/exporting/agent/factory.py +95 -0
  29. waldiez/exporting/agent/processor.py +150 -0
  30. waldiez/exporting/agent/system_message.py +36 -0
  31. waldiez/exporting/agent/termination.py +50 -0
  32. waldiez/exporting/chats/__init__.py +7 -3
  33. waldiez/exporting/chats/exporter.py +97 -0
  34. waldiez/exporting/chats/factory.py +65 -0
  35. waldiez/exporting/chats/processor.py +226 -0
  36. waldiez/exporting/chats/utils/__init__.py +6 -5
  37. waldiez/exporting/chats/utils/common.py +11 -45
  38. waldiez/exporting/chats/utils/group.py +55 -0
  39. waldiez/exporting/chats/utils/nested.py +37 -52
  40. waldiez/exporting/chats/utils/sequential.py +72 -61
  41. waldiez/exporting/chats/utils/{single_chat.py → single.py} +48 -50
  42. waldiez/exporting/core/__init__.py +196 -0
  43. waldiez/exporting/core/constants.py +17 -0
  44. waldiez/exporting/core/content.py +69 -0
  45. waldiez/exporting/core/context.py +244 -0
  46. waldiez/exporting/core/enums.py +89 -0
  47. waldiez/exporting/core/errors.py +19 -0
  48. waldiez/exporting/core/exporter.py +390 -0
  49. waldiez/exporting/core/exporters.py +67 -0
  50. waldiez/exporting/core/extras/__init__.py +39 -0
  51. waldiez/exporting/core/extras/agent_extras/__init__.py +27 -0
  52. waldiez/exporting/core/extras/agent_extras/captain_extras.py +57 -0
  53. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +102 -0
  54. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +53 -0
  55. waldiez/exporting/core/extras/agent_extras/reasoning_extras.py +68 -0
  56. waldiez/exporting/core/extras/agent_extras/standard_extras.py +263 -0
  57. waldiez/exporting/core/extras/base.py +241 -0
  58. waldiez/exporting/core/extras/chat_extras.py +118 -0
  59. waldiez/exporting/core/extras/flow_extras.py +70 -0
  60. waldiez/exporting/core/extras/model_extras.py +73 -0
  61. waldiez/exporting/core/extras/path_resolver.py +93 -0
  62. waldiez/exporting/core/extras/serializer.py +138 -0
  63. waldiez/exporting/core/extras/tool_extras.py +82 -0
  64. waldiez/exporting/core/protocols.py +259 -0
  65. waldiez/exporting/core/result.py +705 -0
  66. waldiez/exporting/core/types.py +329 -0
  67. waldiez/exporting/core/utils/__init__.py +11 -0
  68. waldiez/exporting/core/utils/comment.py +33 -0
  69. waldiez/exporting/core/utils/llm_config.py +117 -0
  70. waldiez/exporting/core/validation.py +96 -0
  71. waldiez/exporting/flow/__init__.py +6 -2
  72. waldiez/exporting/flow/execution_generator.py +193 -0
  73. waldiez/exporting/flow/exporter.py +107 -0
  74. waldiez/exporting/flow/factory.py +94 -0
  75. waldiez/exporting/flow/file_generator.py +214 -0
  76. waldiez/exporting/flow/merger.py +387 -0
  77. waldiez/exporting/flow/orchestrator.py +411 -0
  78. waldiez/exporting/flow/utils/__init__.py +9 -36
  79. waldiez/exporting/flow/utils/common.py +206 -0
  80. waldiez/exporting/flow/utils/importing.py +373 -0
  81. waldiez/exporting/flow/utils/linting.py +200 -0
  82. waldiez/exporting/flow/utils/{logging_utils.py → logging.py} +23 -9
  83. waldiez/exporting/models/__init__.py +3 -1
  84. waldiez/exporting/models/exporter.py +233 -0
  85. waldiez/exporting/models/factory.py +66 -0
  86. waldiez/exporting/models/processor.py +139 -0
  87. waldiez/exporting/tools/__init__.py +11 -0
  88. waldiez/exporting/tools/exporter.py +207 -0
  89. waldiez/exporting/tools/factory.py +57 -0
  90. waldiez/exporting/tools/processor.py +248 -0
  91. waldiez/exporting/tools/registration.py +133 -0
  92. waldiez/io/__init__.py +128 -0
  93. waldiez/io/_ws.py +199 -0
  94. waldiez/io/models/__init__.py +60 -0
  95. waldiez/io/models/base.py +66 -0
  96. waldiez/io/models/constants.py +78 -0
  97. waldiez/io/models/content/__init__.py +23 -0
  98. waldiez/io/models/content/audio.py +43 -0
  99. waldiez/io/models/content/base.py +45 -0
  100. waldiez/io/models/content/file.py +43 -0
  101. waldiez/io/models/content/image.py +96 -0
  102. waldiez/io/models/content/text.py +37 -0
  103. waldiez/io/models/content/video.py +43 -0
  104. waldiez/io/models/user_input.py +269 -0
  105. waldiez/io/models/user_response.py +215 -0
  106. waldiez/io/mqtt.py +681 -0
  107. waldiez/io/redis.py +782 -0
  108. waldiez/io/structured.py +419 -0
  109. waldiez/io/utils.py +184 -0
  110. waldiez/io/ws.py +298 -0
  111. waldiez/logger.py +481 -0
  112. waldiez/models/__init__.py +108 -51
  113. waldiez/models/agents/__init__.py +34 -70
  114. waldiez/models/agents/agent/__init__.py +10 -4
  115. waldiez/models/agents/agent/agent.py +466 -65
  116. waldiez/models/agents/agent/agent_data.py +119 -47
  117. waldiez/models/agents/agent/agent_type.py +13 -2
  118. waldiez/models/agents/agent/code_execution.py +12 -12
  119. waldiez/models/agents/agent/human_input_mode.py +8 -0
  120. waldiez/models/agents/agent/{linked_skill.py → linked_tool.py} +7 -7
  121. waldiez/models/agents/agent/nested_chat.py +35 -7
  122. waldiez/models/agents/agent/termination_message.py +30 -22
  123. waldiez/models/agents/{swarm_agent → agent}/update_system_message.py +22 -22
  124. waldiez/models/agents/agents.py +58 -63
  125. waldiez/models/agents/assistant/assistant.py +4 -4
  126. waldiez/models/agents/assistant/assistant_data.py +13 -1
  127. waldiez/models/agents/{captain_agent → captain}/captain_agent.py +5 -5
  128. waldiez/models/agents/{captain_agent → captain}/captain_agent_data.py +5 -5
  129. waldiez/models/agents/extra_requirements.py +11 -16
  130. waldiez/models/agents/group_manager/group_manager.py +103 -13
  131. waldiez/models/agents/group_manager/group_manager_data.py +36 -14
  132. waldiez/models/agents/group_manager/speakers.py +77 -24
  133. waldiez/models/agents/{rag_user → rag_user_proxy}/__init__.py +16 -16
  134. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +64 -0
  135. waldiez/models/agents/{rag_user/rag_user_data.py → rag_user_proxy/rag_user_proxy_data.py} +6 -5
  136. waldiez/models/agents/{rag_user → rag_user_proxy}/retrieve_config.py +182 -114
  137. waldiez/models/agents/{rag_user → rag_user_proxy}/vector_db_config.py +13 -13
  138. waldiez/models/agents/reasoning/reasoning_agent.py +6 -6
  139. waldiez/models/agents/reasoning/reasoning_agent_data.py +110 -63
  140. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +38 -10
  141. waldiez/models/agents/user_proxy/user_proxy.py +11 -7
  142. waldiez/models/agents/user_proxy/user_proxy_data.py +2 -2
  143. waldiez/models/chat/__init__.py +2 -1
  144. waldiez/models/chat/chat.py +166 -87
  145. waldiez/models/chat/chat_data.py +99 -136
  146. waldiez/models/chat/chat_message.py +33 -23
  147. waldiez/models/chat/chat_nested.py +31 -30
  148. waldiez/models/chat/chat_summary.py +10 -8
  149. waldiez/models/common/__init__.py +52 -2
  150. waldiez/models/common/ag2_version.py +1 -1
  151. waldiez/models/common/base.py +38 -7
  152. waldiez/models/common/dict_utils.py +42 -17
  153. waldiez/models/common/handoff.py +459 -0
  154. waldiez/models/common/id_generator.py +19 -0
  155. waldiez/models/common/method_utils.py +130 -68
  156. waldiez/{exporting/base/utils → models/common}/naming.py +38 -61
  157. waldiez/models/common/waldiez_version.py +37 -0
  158. waldiez/models/flow/__init__.py +9 -2
  159. waldiez/models/flow/connection.py +18 -0
  160. waldiez/models/flow/flow.py +311 -215
  161. waldiez/models/flow/flow_data.py +207 -40
  162. waldiez/models/flow/info.py +85 -0
  163. waldiez/models/flow/naming.py +131 -0
  164. waldiez/models/model/__init__.py +7 -1
  165. waldiez/models/model/extra_requirements.py +3 -12
  166. waldiez/models/model/model.py +76 -21
  167. waldiez/models/model/model_data.py +108 -20
  168. waldiez/models/tool/__init__.py +16 -0
  169. waldiez/models/tool/extra_requirements.py +36 -0
  170. waldiez/models/{skill/skill.py → tool/tool.py} +88 -88
  171. waldiez/models/tool/tool_data.py +51 -0
  172. waldiez/models/tool/tool_type.py +8 -0
  173. waldiez/models/waldiez.py +97 -80
  174. waldiez/runner.py +114 -49
  175. waldiez/running/__init__.py +1 -1
  176. waldiez/running/environment.py +49 -68
  177. waldiez/running/gen_seq_diagram.py +16 -14
  178. waldiez/running/running.py +53 -34
  179. waldiez/utils/__init__.py +0 -4
  180. waldiez/utils/cli_extras/jupyter.py +5 -3
  181. waldiez/utils/cli_extras/runner.py +6 -4
  182. waldiez/utils/cli_extras/studio.py +6 -4
  183. waldiez/utils/conflict_checker.py +15 -9
  184. waldiez/utils/flaml_warnings.py +5 -5
  185. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/METADATA +235 -91
  186. waldiez-0.4.8.dist-info/RECORD +200 -0
  187. waldiez/exporting/agent/agent_exporter.py +0 -297
  188. waldiez/exporting/agent/utils/__init__.py +0 -23
  189. waldiez/exporting/agent/utils/captain_agent.py +0 -263
  190. waldiez/exporting/agent/utils/code_execution.py +0 -65
  191. waldiez/exporting/agent/utils/group_manager.py +0 -220
  192. waldiez/exporting/agent/utils/rag_user/__init__.py +0 -7
  193. waldiez/exporting/agent/utils/rag_user/rag_user.py +0 -209
  194. waldiez/exporting/agent/utils/reasoning.py +0 -36
  195. waldiez/exporting/agent/utils/swarm_agent.py +0 -469
  196. waldiez/exporting/agent/utils/teachability.py +0 -41
  197. waldiez/exporting/agent/utils/termination_message.py +0 -44
  198. waldiez/exporting/base/__init__.py +0 -25
  199. waldiez/exporting/base/agent_position.py +0 -75
  200. waldiez/exporting/base/base_exporter.py +0 -118
  201. waldiez/exporting/base/export_position.py +0 -48
  202. waldiez/exporting/base/import_position.py +0 -23
  203. waldiez/exporting/base/mixin.py +0 -137
  204. waldiez/exporting/base/utils/__init__.py +0 -18
  205. waldiez/exporting/base/utils/comments.py +0 -96
  206. waldiez/exporting/base/utils/path_check.py +0 -68
  207. waldiez/exporting/base/utils/to_string.py +0 -84
  208. waldiez/exporting/chats/chats_exporter.py +0 -240
  209. waldiez/exporting/chats/utils/swarm.py +0 -210
  210. waldiez/exporting/flow/flow_exporter.py +0 -528
  211. waldiez/exporting/flow/utils/agent_utils.py +0 -204
  212. waldiez/exporting/flow/utils/chat_utils.py +0 -71
  213. waldiez/exporting/flow/utils/def_main.py +0 -77
  214. waldiez/exporting/flow/utils/flow_content.py +0 -202
  215. waldiez/exporting/flow/utils/flow_names.py +0 -116
  216. waldiez/exporting/flow/utils/importing_utils.py +0 -227
  217. waldiez/exporting/models/models_exporter.py +0 -199
  218. waldiez/exporting/models/utils.py +0 -174
  219. waldiez/exporting/skills/__init__.py +0 -9
  220. waldiez/exporting/skills/skills_exporter.py +0 -176
  221. waldiez/exporting/skills/utils.py +0 -369
  222. waldiez/models/agents/agent/teachability.py +0 -70
  223. waldiez/models/agents/rag_user/rag_user.py +0 -60
  224. waldiez/models/agents/swarm_agent/__init__.py +0 -50
  225. waldiez/models/agents/swarm_agent/after_work.py +0 -179
  226. waldiez/models/agents/swarm_agent/on_condition.py +0 -105
  227. waldiez/models/agents/swarm_agent/on_condition_available.py +0 -142
  228. waldiez/models/agents/swarm_agent/on_condition_target.py +0 -40
  229. waldiez/models/agents/swarm_agent/swarm_agent.py +0 -107
  230. waldiez/models/agents/swarm_agent/swarm_agent_data.py +0 -124
  231. waldiez/models/flow/utils.py +0 -232
  232. waldiez/models/skill/__init__.py +0 -16
  233. waldiez/models/skill/extra_requirements.py +0 -36
  234. waldiez/models/skill/skill_data.py +0 -53
  235. waldiez/models/skill/skill_type.py +0 -8
  236. waldiez/utils/pysqlite3_checker.py +0 -308
  237. waldiez/utils/rdps_checker.py +0 -122
  238. waldiez-0.4.7.dist-info/RECORD +0 -149
  239. /waldiez/models/agents/{captain_agent → captain}/__init__.py +0 -0
  240. /waldiez/models/agents/{captain_agent → captain}/captain_agent_lib_entry.py +0 -0
  241. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/WHEEL +0 -0
  242. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/entry_points.txt +0 -0
  243. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/licenses/LICENSE +0 -0
  244. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/licenses/NOTICE.md +0 -0
@@ -1,5 +1,7 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ # pyright: reportUnknownMemberType=false,reportUnknownVariableType=false
4
+ # pyright: reportUnknownArgumentType=false
3
5
  """Function related utilities."""
4
6
 
5
7
  import ast
@@ -7,15 +9,25 @@ import importlib.util
7
9
  import sys
8
10
  import sysconfig
9
11
  from pathlib import Path
10
- from typing import List, Optional, Tuple
12
+ from typing import NamedTuple, Optional
11
13
 
14
+ # parso for extracting function bodies
15
+ # (keeps comments, docstrings and formatting as-is)
12
16
  import parso
13
- import parso.python
14
- import parso.tree
17
+ import parso.python # pyright: ignore
18
+ import parso.tree # pyright: ignore
15
19
 
20
+ # let's limit the variable name length
16
21
  MAX_VAR_NAME_LENGTH = 64
17
22
 
18
23
 
24
+ class ParseResult(NamedTuple):
25
+ """Result of parsing a code string."""
26
+
27
+ error: Optional[str]
28
+ tree: Optional[ast.Module]
29
+
30
+
19
31
  def is_standard_library(module_name: str) -> bool:
20
32
  """Check if the module is part of the standard library.
21
33
 
@@ -33,21 +45,21 @@ def is_standard_library(module_name: str) -> bool:
33
45
  return True
34
46
  try:
35
47
  spec = importlib.util.find_spec(module_name)
36
- except BaseException: # pylint: disable=broad-except
48
+ except (ImportError, ValueError, ModuleNotFoundError): # pragma: no cover
37
49
  return False
38
- if spec is None or not spec.origin:
50
+ if spec is None or not spec.origin: # pragma: no cover
39
51
  return False
40
52
  if "site-packages" in spec.origin:
41
53
  return False
42
- if spec.origin.startswith(sys.prefix) or spec.origin == "frozen":
54
+ if spec.origin == "frozen":
43
55
  return True
44
- stdlib_path = str(Path(sysconfig.get_path("stdlib")).resolve())
45
- return spec.origin.startswith(stdlib_path)
56
+ stdlib_path = Path(sysconfig.get_path("stdlib")).resolve()
57
+ return Path(spec.origin).resolve().is_relative_to(stdlib_path)
46
58
 
47
59
 
48
60
  def parse_code_string(
49
61
  code_string: str,
50
- ) -> Tuple[Optional[str], Optional[ast.Module]]:
62
+ ) -> ParseResult:
51
63
  """Parse the code string.
52
64
 
53
65
  Parameters
@@ -57,7 +69,7 @@ def parse_code_string(
57
69
 
58
70
  Returns
59
71
  -------
60
- Tuple[Optional[str], Optional[ast.Module]]
72
+ ParseResult
61
73
  If valid, None and the ast module.
62
74
  If invalid, the error message and None.
63
75
  """
@@ -65,16 +77,95 @@ def parse_code_string(
65
77
  try:
66
78
  tree = ast.parse(code_string)
67
79
  except SyntaxError as e:
68
- return f"SyntaxError: {e}, in " + "\n" + f"{code_string}", None
80
+ return ParseResult(
81
+ f"SyntaxError: {e}, in " + "\n" + f"{code_string}", None
82
+ )
69
83
  except BaseException as e: # pragma: no cover
70
- return f"Invalid code: {e}, in " + "\n" + f"{code_string}", None
71
- return None, tree
84
+ return ParseResult(
85
+ f"Invalid code: {e}, in " + "\n" + f"{code_string}", None
86
+ )
87
+ return ParseResult(None, tree)
88
+
89
+
90
+ def _extract_module_name(node: ast.AST) -> Optional[str]:
91
+ """Extract the root module name from an import node."""
92
+ if isinstance(node, ast.Import):
93
+ return node.names[0].name.split(".")[0]
94
+ if isinstance(node, ast.ImportFrom):
95
+ if node.module:
96
+ # Handle relative imports
97
+ if node.module.startswith("."): # pragma: no cover
98
+ return None # Skip relative imports for stdlib check
99
+ return node.module.split(".")[0]
100
+ return None # pragma: no cover
101
+
102
+
103
+ def _extract_imports_from_ast(code_string: str) -> tuple[list[str], list[str]]:
104
+ """Extract import statements from code using AST.
105
+
106
+ Parameters
107
+ ----------
108
+ code_string : str
109
+ The code string to parse.
110
+
111
+ Returns
112
+ -------
113
+ tuple[list[str], list[str]]
114
+ Standard library imports and third party imports (unsorted).
115
+ """
116
+ standard_lib_imports: list[str] = []
117
+ third_party_imports: list[str] = []
118
+
119
+ try:
120
+ tree = ast.parse(code_string)
121
+ except SyntaxError: # pragma: no cover
122
+ return [], []
123
+
124
+ for node in ast.walk(tree):
125
+ if isinstance(node, (ast.Import, ast.ImportFrom)):
126
+ full_import_statement = ast.get_source_segment(code_string, node)
127
+ if not full_import_statement: # pragma: no cover
128
+ continue
129
+ full_import_statement = full_import_statement.strip()
130
+
131
+ module_name = _extract_module_name(node)
132
+ if not module_name: # pragma: no cover
133
+ continue
134
+
135
+ if is_standard_library(module_name):
136
+ standard_lib_imports.append(full_import_statement)
137
+ else:
138
+ third_party_imports.append(full_import_statement)
139
+
140
+ return standard_lib_imports, third_party_imports
141
+
142
+
143
+ def _sort_imports(imports: list[str]) -> list[str]:
144
+ """Sort import statements with 'import' statements before 'from' statements.
145
+
146
+ Parameters
147
+ ----------
148
+ imports : list[str]
149
+ List of import statements to sort.
150
+
151
+ Returns
152
+ -------
153
+ list[str]
154
+ Sorted import statements.
155
+ """
156
+ import_statements = sorted(
157
+ [stmt for stmt in imports if stmt.startswith("import ")]
158
+ )
159
+ from_statements = sorted(
160
+ [stmt for stmt in imports if stmt.startswith("from ")]
161
+ )
162
+ return import_statements + from_statements
72
163
 
73
164
 
74
165
  def gather_code_imports(
75
166
  code_string: str,
76
167
  is_interop: bool,
77
- ) -> Tuple[List[str], List[str]]:
168
+ ) -> tuple[list[str], list[str]]:
78
169
  """Gather the imports from the code string.
79
170
 
80
171
  Parameters
@@ -86,35 +177,13 @@ def gather_code_imports(
86
177
 
87
178
  Returns
88
179
  -------
89
- Tuple[List[str], List[str]]
180
+ tuple[list[str], list[str]]
90
181
  The standard library imports and the third party imports.
91
182
  """
92
- standard_lib_imports: List[str] = []
93
- third_party_imports: List[str] = []
94
- tree = parso.parse(code_string) # type: ignore
95
- for node in tree.iter_imports():
96
- if node.type == "import_name":
97
- full_import_statement = node.get_code().strip()
98
- module_name = (
99
- node.get_code().replace("import", "").strip().split(" ")[0]
100
- )
101
- if not module_name:
102
- continue
103
- if is_standard_library(module_name):
104
- standard_lib_imports.append(full_import_statement)
105
- else:
106
- third_party_imports.append(full_import_statement)
107
- elif node.type == "import_from":
108
- full_import_statement = node.get_code().strip()
109
- module_name = (
110
- node.get_code().replace("from", "").strip().split(" ")[0]
111
- )
112
- if not module_name:
113
- continue
114
- if is_standard_library(module_name):
115
- standard_lib_imports.append(full_import_statement)
116
- else:
117
- third_party_imports.append(full_import_statement)
183
+ standard_lib_imports, third_party_imports = _extract_imports_from_ast(
184
+ code_string
185
+ )
186
+
118
187
  if is_interop and (
119
188
  "from autogen.interop import Interoperability"
120
189
  not in third_party_imports
@@ -122,25 +191,18 @@ def gather_code_imports(
122
191
  third_party_imports.append(
123
192
  "from autogen.interop import Interoperability"
124
193
  )
125
- # sorted_standard_lib_imports = # first import x, then from a import b
126
- sorted_standard_lib_imports = sorted(
127
- [stmt for stmt in standard_lib_imports if stmt.startswith("import ")]
128
- ) + sorted(
129
- [stmt for stmt in standard_lib_imports if stmt.startswith("from ")]
130
- )
131
- sorted_third_party_imports = sorted(
132
- [stmt for stmt in third_party_imports if stmt.startswith("import ")]
133
- ) + sorted(
134
- [stmt for stmt in third_party_imports if stmt.startswith("from ")]
135
- )
194
+
195
+ sorted_standard_lib_imports = _sort_imports(standard_lib_imports)
196
+ sorted_third_party_imports = _sort_imports(third_party_imports)
197
+
136
198
  return sorted_standard_lib_imports, sorted_third_party_imports
137
199
 
138
200
 
139
201
  def check_function(
140
202
  code_string: str,
141
203
  function_name: str,
142
- function_args: List[str],
143
- ) -> Tuple[bool, str]:
204
+ function_args: list[str],
205
+ ) -> tuple[bool, str]:
144
206
  """Check the function.
145
207
 
146
208
  Parameters
@@ -149,12 +211,12 @@ def check_function(
149
211
  The code string to check.
150
212
  function_name : str
151
213
  The expected method name.
152
- function_args : List[str]
214
+ function_args : list[str]
153
215
  The expected method arguments.
154
216
 
155
217
  Returns
156
218
  -------
157
- Tuple[bool, str]
219
+ tuple[bool, str]
158
220
  If valid, True and the function body (only), no extra lines.
159
221
  If invalid, False and the error message.
160
222
  """
@@ -173,24 +235,24 @@ def _validate_function_body(
173
235
  tree: ast.Module,
174
236
  code_string: str,
175
237
  function_name: str,
176
- function_args: List[str],
177
- ) -> Tuple[bool, str]:
238
+ function_args: list[str],
239
+ ) -> tuple[bool, str]:
178
240
  """Get the function body.
179
241
 
180
242
  Parameters
181
243
  ----------
182
244
  tree : ast.Module
183
245
  The ast module.
184
- function_body : str
246
+ code_string : str
185
247
  The function body.
186
248
  function_name : str
187
249
  The expected method name.
188
- function_args : List[str]
250
+ function_args : list[str]
189
251
  The expected method arguments.
190
252
 
191
253
  Returns
192
254
  -------
193
- Tuple[bool, str]
255
+ tuple[bool, str]
194
256
  If valid, True and the function body (only), no extra lines.
195
257
  If invalid, False and the error message.
196
258
  """
@@ -277,13 +339,13 @@ def _get_function_body(
277
339
  Raises
278
340
  ------
279
341
  ValueError
280
- If no body found in the function.
342
+ If the function's body is empty.
281
343
  """
282
344
  lines = code_string.splitlines()
283
345
  signature_start_line = node.lineno - 1
284
346
  body_start_line = node.body[0].lineno - 1
285
347
  signature_end_line = signature_start_line
286
- for i in range(signature_start_line, body_start_line):
348
+ for i in range(signature_start_line, body_start_line): # pragma: no branch
287
349
  if ")" in lines[i]:
288
350
  signature_end_line = i
289
351
  break
@@ -301,8 +363,8 @@ def _get_function_body(
301
363
 
302
364
  def generate_function(
303
365
  function_name: str,
304
- function_args: List[str],
305
- function_types: Tuple[List[str], str],
366
+ function_args: list[str],
367
+ function_types: tuple[list[str], str],
306
368
  function_body: str,
307
369
  types_as_comments: bool = False,
308
370
  ) -> str:
@@ -312,9 +374,9 @@ def generate_function(
312
374
  ----------
313
375
  function_name : str
314
376
  The function name.
315
- function_args : List[str]
377
+ function_args : list[str]
316
378
  The function arguments.
317
- function_types : Tuple[List[str], str]
379
+ function_types : tuple[list[str], str]
318
380
  The function types.
319
381
  function_body : str
320
382
  The function body.
@@ -349,6 +411,6 @@ def generate_function(
349
411
  function_string += " -> " + function_types[1] + ":"
350
412
  function_string += "\n" if not function_body.startswith("\n") else ""
351
413
  function_string += f"{function_body}"
352
- if not function_string.endswith("\n"):
414
+ if not function_string.endswith("\n"): # pragma: no branch
353
415
  function_string += "\n"
354
416
  return function_string
@@ -1,19 +1,8 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
- """Utilities for naming.
4
-
5
- Functions
6
- ---------
7
- get_valid_python_variable_name
8
- Make sure a string is a valid Python variable name.
9
- get_valid_instance_name
10
- Get a valid instance name.
11
- get_escaped_string
12
- Get a string with escaped quotes and newlines.
13
- """
3
+ """Ensure unique names for agents, models, tools, and chats."""
14
4
 
15
5
  import re
16
- from typing import Dict, Tuple
17
6
 
18
7
  MAX_VARIABLE_LENGTH = 46
19
8
 
@@ -29,10 +18,8 @@ def get_valid_python_variable_name(
29
18
  ----------
30
19
  possible : str
31
20
  The possible name.
32
-
33
21
  prefix : str, optional
34
22
  The prefix to use if the name starts with a digit or special character
35
-
36
23
  max_length : int, optional
37
24
  The maximum length of the variable name.
38
25
 
@@ -41,47 +28,48 @@ def get_valid_python_variable_name(
41
28
  str
42
29
  The valid Python variable name.
43
30
  """
31
+ if not possible or not possible.strip():
32
+ return prefix + "_"
33
+
34
+ # First handle arrow operators specifically
35
+ possible = possible.replace("->", "to")
36
+ possible = possible.replace("=>", "to")
37
+ possible = possible.replace("<-", "from")
38
+ possible = possible.replace("<=", "from")
39
+
40
+ # Replace non-ASCII characters and non-word characters with underscores
41
+ # \W matches any non-word character, but in Python's re module,
42
+ # \w includes Unicode letters by default, so we need to be more explicit
43
+ # to replace Unicode letters with underscores for valid Python identifiers
44
+ possible = re.sub(r"[^\w]", "_", possible) # Replace non-word chars
45
+ possible = re.sub(r"[^\x00-\x7F]", "_", possible) # Replace non-ASCII chars
46
+
47
+ # Convert to lowercase and truncate
48
+ possible = possible.lower()[:max_length]
44
49
 
45
- def replacement(match: re.Match[str]) -> str:
46
- """Get the replacement for the match.
47
-
48
- Parameters
49
- ----------
50
- match : re.Match[str]
51
- The match.
52
-
53
- Returns
54
- -------
55
- str
56
- The replacement
57
- """
58
- if match.group(0) in ["->", "=>"]:
59
- return "to"
60
- if match.group(0) in ["<-", "<="]:
61
- return "from"
62
- if re.match(r"\W|^(?=\d)", match.group(0)):
63
- return "_"
64
- return match.group(0)
65
-
66
- possible = re.sub(r"->|=>|<-|<=|\W|^(?=\d)", replacement, possible)[
67
- :max_length
68
- ].lower()
50
+ # Remove trailing underscores from truncation
51
+ possible = possible.rstrip("_")
69
52
 
70
53
  if not possible:
71
54
  return prefix + "_"
55
+
56
+ # Handle names starting with underscore
72
57
  if possible.startswith("_"):
73
58
  return f"{prefix}{possible}"
59
+
60
+ # Handle names starting with digit
74
61
  if possible[0].isdigit():
75
62
  return f"{prefix}_{possible}"
63
+
76
64
  return possible
77
65
 
78
66
 
79
67
  def get_valid_instance_name(
80
- instance: Tuple[str, str],
81
- current_names: Dict[str, str],
68
+ instance: tuple[str, str],
69
+ current_names: dict[str, str],
82
70
  prefix: str = "w",
83
71
  max_length: int = MAX_VARIABLE_LENGTH,
84
- ) -> Dict[str, str]:
72
+ ) -> dict[str, str]:
85
73
  """Get a valid instance name.
86
74
 
87
75
  If the instance id is already in the current names nothing is done.
@@ -90,9 +78,9 @@ def get_valid_instance_name(
90
78
 
91
79
  Parameters
92
80
  ----------
93
- instance : Tuple[str, str]
81
+ instance : tuple[str, str]
94
82
  The instance id and possible name.
95
- current_names : Dict[str, str]
83
+ current_names : dict[str, str]
96
84
  The current names.
97
85
  prefix : str, optional
98
86
  The prefix to use if the name starts with a digit,
@@ -103,39 +91,28 @@ def get_valid_instance_name(
103
91
 
104
92
  Returns
105
93
  -------
106
- Dict[str, str]
94
+ dict[str, str]
107
95
  The updated names.
108
96
  """
109
97
  instance_id, possible_name = instance[0], instance[1][:max_length]
98
+
110
99
  if instance_id in current_names:
111
- # already in the current names (it's id)
100
+ # already in the current names (by its id)
112
101
  return current_names
102
+
113
103
  new_names = current_names.copy()
114
104
  name = get_valid_python_variable_name(
115
105
  possible_name, prefix=prefix, max_length=max_length
116
106
  )
107
+
117
108
  if name in current_names.values():
118
109
  name = f"{prefix}_{name}"
110
+
119
111
  if name in current_names.values():
120
112
  index = 1
121
113
  while f"{name}_{index}" in current_names.values():
122
114
  index += 1
123
115
  name = f"{name}_{index}"
116
+
124
117
  new_names[instance_id] = name
125
118
  return new_names
126
-
127
-
128
- def get_escaped_string(string: str) -> str:
129
- """Get a string with escaped quotes and newlines.
130
-
131
- Parameters
132
- ----------
133
- string : str
134
- The original string.
135
-
136
- Returns
137
- -------
138
- str
139
- The escaped string.
140
- """
141
- return string.replace('"', '\\"').replace("\n", "\\n")
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
4
+ """Read the version from the version file."""
5
+
6
+ from pathlib import Path
7
+
8
+
9
+ def get_waldiez_version() -> (
10
+ str
11
+ ): # pragma: no cover # depends on file existence
12
+ """Read the version from the version file.
13
+
14
+ Returns
15
+ -------
16
+ str
17
+ The version.
18
+ """
19
+ here = Path(__file__).parent # waldiez/models/common
20
+ package_dir = here.parent.parent # waldiez/models -> waldiez
21
+ version_file = package_dir / "_version.py"
22
+ if not version_file.exists():
23
+ return "0.0.0" # dev / ignored
24
+ with version_file.open() as f:
25
+ for line in f:
26
+ if line.startswith("__version__"):
27
+ version = line.split("=")[1].strip().strip('"').strip("'")
28
+ # send version without "v" prefix
29
+ if version.startswith("v"):
30
+ version = version[1:]
31
+ # make sure it is a valid semver
32
+ if not version or not all(
33
+ part.isdigit() for part in version.split(".")
34
+ ):
35
+ return "0.0.0"
36
+ return version
37
+ return "0.0.0" # fallback if not found
@@ -2,12 +2,19 @@
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
3
  """Waldiez flow related models."""
4
4
 
5
+ from .connection import WaldiezAgentConnection
5
6
  from .flow import WaldiezFlow
6
- from .flow_data import WaldiezFlowData
7
- from .utils import get_flow_data
7
+ from .flow_data import WaldiezFlowData, get_flow_data
8
+ from .info import WaldiezAgentInfo, WaldiezFlowInfo
9
+ from .naming import WaldiezUniqueNames, ensure_unique_names
8
10
 
9
11
  __all__ = [
10
12
  "get_flow_data",
13
+ "ensure_unique_names",
14
+ "WaldiezAgentConnection",
15
+ "WaldiezAgentInfo",
11
16
  "WaldiezFlow",
17
+ "WaldiezFlowInfo",
12
18
  "WaldiezFlowData",
19
+ "WaldiezUniqueNames",
13
20
  ]
@@ -0,0 +1,18 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ """Waldiez agent connection model."""
4
+
5
+ from typing import TypedDict
6
+
7
+ from ..agents import (
8
+ WaldiezAgent,
9
+ )
10
+ from ..chat import WaldiezChat
11
+
12
+
13
+ class WaldiezAgentConnection(TypedDict):
14
+ """Agent connection."""
15
+
16
+ source: WaldiezAgent
17
+ target: WaldiezAgent
18
+ chat: WaldiezChat