zrb 1.21.9__py3-none-any.whl → 1.21.31__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 zrb might be problematic. Click here for more details.

Files changed (48) hide show
  1. zrb/attr/type.py +10 -7
  2. zrb/builtin/git.py +12 -1
  3. zrb/builtin/llm/chat_completion.py +287 -0
  4. zrb/builtin/llm/chat_session_cmd.py +90 -28
  5. zrb/builtin/llm/chat_trigger.py +6 -1
  6. zrb/builtin/llm/history.py +4 -4
  7. zrb/builtin/llm/tool/cli.py +25 -13
  8. zrb/builtin/llm/tool/code.py +9 -2
  9. zrb/builtin/llm/tool/file.py +42 -81
  10. zrb/builtin/llm/tool/note.py +36 -16
  11. zrb/builtin/llm/tool/search/__init__.py +1 -0
  12. zrb/builtin/llm/tool/search/brave.py +60 -0
  13. zrb/builtin/llm/tool/search/searxng.py +55 -0
  14. zrb/builtin/llm/tool/search/serpapi.py +55 -0
  15. zrb/builtin/llm/tool/sub_agent.py +30 -10
  16. zrb/builtin/llm/tool/web.py +12 -72
  17. zrb/config/config.py +108 -13
  18. zrb/config/default_prompt/interactive_system_prompt.md +1 -1
  19. zrb/config/default_prompt/summarization_prompt.md +54 -8
  20. zrb/config/default_prompt/system_prompt.md +1 -1
  21. zrb/config/llm_rate_limitter.py +24 -5
  22. zrb/input/option_input.py +13 -1
  23. zrb/task/llm/agent.py +42 -144
  24. zrb/task/llm/agent_runner.py +152 -0
  25. zrb/task/llm/config.py +7 -5
  26. zrb/task/llm/conversation_history.py +35 -24
  27. zrb/task/llm/conversation_history_model.py +4 -11
  28. zrb/task/llm/default_workflow/coding/workflow.md +2 -3
  29. zrb/task/llm/file_replacement.py +206 -0
  30. zrb/task/llm/file_tool_model.py +57 -0
  31. zrb/task/llm/history_processor.py +206 -0
  32. zrb/task/llm/history_summarization.py +2 -179
  33. zrb/task/llm/print_node.py +14 -5
  34. zrb/task/llm/prompt.py +7 -18
  35. zrb/task/llm/subagent_conversation_history.py +41 -0
  36. zrb/task/llm/tool_confirmation_completer.py +41 -0
  37. zrb/task/llm/tool_wrapper.py +26 -12
  38. zrb/task/llm_task.py +55 -47
  39. zrb/util/attr.py +17 -10
  40. zrb/util/cli/text.py +6 -4
  41. zrb/util/git.py +2 -2
  42. zrb/util/yaml.py +1 -0
  43. zrb/xcom/xcom.py +10 -0
  44. {zrb-1.21.9.dist-info → zrb-1.21.31.dist-info}/METADATA +5 -5
  45. {zrb-1.21.9.dist-info → zrb-1.21.31.dist-info}/RECORD +47 -37
  46. zrb/task/llm/history_summarization_tool.py +0 -24
  47. {zrb-1.21.9.dist-info → zrb-1.21.31.dist-info}/WHEEL +0 -0
  48. {zrb-1.21.9.dist-info → zrb-1.21.31.dist-info}/entry_points.txt +0 -0
zrb/task/llm/prompt.py CHANGED
@@ -115,11 +115,11 @@ def _construct_system_prompt(
115
115
  ),
116
116
  ),
117
117
  make_markdown_section(
118
- "🧠 Long Term Note",
118
+ "🧠 Long Term Note Content",
119
119
  conversation_history.long_term_note,
120
120
  ),
121
121
  make_markdown_section(
122
- "📝 Contextual Note",
122
+ "📝 Contextual Note Content",
123
123
  conversation_history.contextual_note,
124
124
  ),
125
125
  make_markdown_section(
@@ -129,21 +129,6 @@ def _construct_system_prompt(
129
129
  ]
130
130
  ),
131
131
  ),
132
- make_markdown_section(
133
- "💬 PAST CONVERSATION",
134
- "\n".join(
135
- [
136
- make_markdown_section(
137
- "Narrative Summary",
138
- conversation_history.past_conversation_summary,
139
- ),
140
- make_markdown_section(
141
- "Past Transcript",
142
- conversation_history.past_conversation_transcript,
143
- ),
144
- ]
145
- ),
146
- ),
147
132
  ]
148
133
  )
149
134
 
@@ -230,7 +215,11 @@ def _get_workflow_prompt(
230
215
  make_markdown_section(
231
216
  workflow_name.capitalize(),
232
217
  (
233
- workflow.content
218
+ (
219
+ "> Workflow status: Automatically Loaded/Activated.\n"
220
+ f"> Workflow location: `{workflow.path}`\n"
221
+ "{workflow.content}"
222
+ )
234
223
  if select_active_workflow
235
224
  else f"Workflow name: {workflow_name}\n{workflow.description}"
236
225
  ),
@@ -0,0 +1,41 @@
1
+ from zrb.context.any_context import AnyContext
2
+ from zrb.task.llm.conversation_history_model import ConversationHistory
3
+ from zrb.task.llm.typing import ListOfDict
4
+ from zrb.xcom.xcom import Xcom
5
+
6
+
7
+ def inject_subagent_conversation_history_into_ctx(
8
+ ctx: AnyContext, conversation_history: ConversationHistory
9
+ ):
10
+ subagent_messages_xcom = _get_global_subagent_history_xcom(ctx)
11
+ existing_subagent_history = subagent_messages_xcom.get({})
12
+ subagent_messages_xcom.set(
13
+ {**existing_subagent_history, **conversation_history.subagent_history}
14
+ )
15
+
16
+
17
+ def extract_subagent_conversation_history_from_ctx(
18
+ ctx: AnyContext,
19
+ ) -> dict[str, ListOfDict]:
20
+ subagent_messsages_xcom = _get_global_subagent_history_xcom(ctx)
21
+ return subagent_messsages_xcom.get({})
22
+
23
+
24
+ def get_ctx_subagent_history(ctx: AnyContext, subagent_name: str) -> ListOfDict:
25
+ subagent_history = extract_subagent_conversation_history_from_ctx(ctx)
26
+ return subagent_history.get(subagent_name, [])
27
+
28
+
29
+ def set_ctx_subagent_history(ctx: AnyContext, subagent_name: str, messages: ListOfDict):
30
+ subagent_history = extract_subagent_conversation_history_from_ctx(ctx)
31
+ subagent_history[subagent_name] = messages
32
+ subagent_messages_xcom = _get_global_subagent_history_xcom(ctx)
33
+ subagent_messages_xcom.set(subagent_history)
34
+
35
+
36
+ def _get_global_subagent_history_xcom(ctx: AnyContext) -> Xcom:
37
+ if "_global_subagents" not in ctx.xcom:
38
+ ctx.xcom["_global_subagents"] = Xcom([{}])
39
+ if not isinstance(ctx.xcom["_global_subagents"], Xcom):
40
+ raise ValueError("ctx.xcom._global_subagents must be an Xcom")
41
+ return ctx.xcom["_global_subagents"]
@@ -0,0 +1,41 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from prompt_toolkit.completion import Completer
5
+
6
+
7
+ def get_tool_confirmation_completer(
8
+ options: list[str], meta_dict: dict[str, str]
9
+ ) -> "Completer":
10
+ from prompt_toolkit.completion import Completer, Completion
11
+
12
+ class ToolConfirmationCompleter(Completer):
13
+ """Custom completer for tool confirmation that doesn't auto-complete partial words."""
14
+
15
+ def __init__(self, options, meta_dict):
16
+ self.options = options
17
+ self.meta_dict = meta_dict
18
+
19
+ def get_completions(self, document, complete_event):
20
+ text = document.text.strip()
21
+ # 1. Input is empty, OR
22
+ # 2. Input exactly matches the beginning of an option
23
+ if text == "":
24
+ # Show all options when nothing is typed
25
+ for option in self.options:
26
+ yield Completion(
27
+ option,
28
+ start_position=0,
29
+ display_meta=self.meta_dict.get(option, ""),
30
+ )
31
+ return
32
+ # Only complete if text exactly matches the beginning of an option
33
+ for option in self.options:
34
+ if option.startswith(text):
35
+ yield Completion(
36
+ option,
37
+ start_position=-len(text),
38
+ display_meta=self.meta_dict.get(option, ""),
39
+ )
40
+
41
+ return ToolConfirmationCompleter(options, meta_dict)
@@ -10,11 +10,12 @@ from zrb.config.config import CFG
10
10
  from zrb.config.llm_rate_limitter import llm_rate_limitter
11
11
  from zrb.context.any_context import AnyContext
12
12
  from zrb.task.llm.error import ToolExecutionError
13
+ from zrb.task.llm.file_replacement import edit_replacement, is_single_path_replacement
14
+ from zrb.task.llm.tool_confirmation_completer import get_tool_confirmation_completer
13
15
  from zrb.util.callable import get_callable_name
14
16
  from zrb.util.cli.markdown import render_markdown
15
17
  from zrb.util.cli.style import (
16
18
  stylize_blue,
17
- stylize_error,
18
19
  stylize_faint,
19
20
  stylize_green,
20
21
  stylize_yellow,
@@ -184,7 +185,7 @@ async def _handle_user_response(
184
185
  ]
185
186
  )
186
187
  ctx.print(complete_confirmation_message, plain=True)
187
- user_response = await _read_line()
188
+ user_response = await _read_line(args, kwargs)
188
189
  ctx.print("", plain=True)
189
190
  new_kwargs, is_edited = _get_edited_kwargs(ctx, user_response, kwargs)
190
191
  if is_edited:
@@ -214,10 +215,19 @@ def _get_edited_kwargs(
214
215
  key_parts = key.split(".")
215
216
  if len(key_parts) > 0 and key_parts[0] not in kwargs:
216
217
  return kwargs, True
218
+ # Handle replacement edit
219
+ if len(kwargs) == 1:
220
+ kwarg_key = list(kwargs.keys())[0]
221
+ if is_single_path_replacement(kwargs[kwarg_key]) and (
222
+ key == "" or key == kwarg_key
223
+ ):
224
+ kwargs[kwarg_key], edited = edit_replacement(kwargs[kwarg_key])
225
+ return kwargs, True
226
+ # Handle other kind of edit
217
227
  old_val_str = yaml_dump(kwargs, key)
218
228
  if val_str == "":
219
229
  val_str = edit_text(
220
- prompt_message=f"# {key}",
230
+ prompt_message=f"# {key}" if key != "" else "",
221
231
  value=old_val_str,
222
232
  editor=CFG.DEFAULT_EDITOR,
223
233
  extension=".yaml",
@@ -240,13 +250,7 @@ def _get_user_approval_and_reason(
240
250
  try:
241
251
  approved = True if approval_str.strip() == "" else to_boolean(approval_str)
242
252
  if not approved and reason == "":
243
- ctx.print(
244
- stylize_error(
245
- f"You must specify rejection reason (i.e., No, <why>) for {func_call_str}" # noqa
246
- ),
247
- plain=True,
248
- )
249
- return None
253
+ reason = "User disapproving the tool execution"
250
254
  return approved, reason
251
255
  except Exception:
252
256
  return False, user_response
@@ -290,11 +294,21 @@ def _truncate_arg(arg: str, length: int = 19) -> str:
290
294
  return normalized_arg
291
295
 
292
296
 
293
- async def _read_line():
297
+ async def _read_line(args: list[Any] | tuple[Any], kwargs: dict[str, Any]):
294
298
  from prompt_toolkit import PromptSession
295
299
 
300
+ options = ["yes", "no", "edit"]
301
+ meta_dict = {
302
+ "yes": "Approve the execution",
303
+ "no": "Disapprove the execution",
304
+ "edit": "Edit tool execution parameters",
305
+ }
306
+ for key in kwargs:
307
+ options.append(f"edit {key}")
308
+ meta_dict[f"edit {key}"] = f"Edit tool execution parameter: {key}"
309
+ completer = get_tool_confirmation_completer(options, meta_dict)
296
310
  reader = PromptSession()
297
- return await reader.prompt_async()
311
+ return await reader.prompt_async(completer=completer)
298
312
 
299
313
 
300
314
  def _adjust_signature(wrapper: Callable, original_sig: inspect.Signature):
zrb/task/llm_task.py CHANGED
@@ -3,13 +3,14 @@ from collections.abc import Callable
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from zrb.attr.type import BoolAttr, IntAttr, StrAttr, StrListAttr, fstring
6
- from zrb.config.llm_rate_limitter import LLMRateLimiter
6
+ from zrb.config.llm_rate_limitter import LLMRateLimitter
7
7
  from zrb.context.any_context import AnyContext
8
8
  from zrb.env.any_env import AnyEnv
9
9
  from zrb.input.any_input import AnyInput
10
10
  from zrb.task.any_task import AnyTask
11
11
  from zrb.task.base_task import BaseTask
12
- from zrb.task.llm.agent import get_agent, run_agent_iteration
12
+ from zrb.task.llm.agent import get_agent
13
+ from zrb.task.llm.agent_runner import run_agent_iteration
13
14
  from zrb.task.llm.config import (
14
15
  get_model,
15
16
  get_model_settings,
@@ -21,13 +22,17 @@ from zrb.task.llm.conversation_history import (
21
22
  write_conversation_history,
22
23
  )
23
24
  from zrb.task.llm.conversation_history_model import ConversationHistory
24
- from zrb.task.llm.history_summarization import maybe_summarize_history
25
+ from zrb.task.llm.history_summarization import get_history_summarization_token_threshold
25
26
  from zrb.task.llm.prompt import (
26
27
  get_attachments,
27
28
  get_summarization_system_prompt,
28
29
  get_system_and_user_prompt,
29
30
  get_user_message,
30
31
  )
32
+ from zrb.task.llm.subagent_conversation_history import (
33
+ extract_subagent_conversation_history_from_ctx,
34
+ inject_subagent_conversation_history_into_ctx,
35
+ )
31
36
  from zrb.task.llm.workflow import load_workflow
32
37
  from zrb.util.cli.style import stylize_faint
33
38
  from zrb.xcom.xcom import Xcom
@@ -50,11 +55,11 @@ class LLMTask(BaseTask):
50
55
  cli_only: bool = False,
51
56
  input: list[AnyInput | None] | AnyInput | None = None,
52
57
  env: list[AnyEnv | None] | AnyEnv | None = None,
53
- model: "Callable[[AnyContext], Model | str | fstring] | Model | None" = None,
58
+ model: "Callable[[AnyContext], Model | str | fstring | None] | Model | None" = None,
54
59
  render_model: bool = True,
55
- model_base_url: StrAttr | None = None,
60
+ model_base_url: "Callable[[AnyContext], str | None] | str | None" = None,
56
61
  render_model_base_url: bool = True,
57
- model_api_key: StrAttr | None = None,
62
+ model_api_key: "Callable[[AnyContext], str | None] | str | None" = None,
58
63
  render_model_api_key: bool = True,
59
64
  model_settings: (
60
65
  "ModelSettings | Callable[[AnyContext], ModelSettings] | None"
@@ -70,16 +75,16 @@ class LLMTask(BaseTask):
70
75
  small_model_settings: (
71
76
  "ModelSettings | Callable[[AnyContext], ModelSettings] | None"
72
77
  ) = None,
73
- persona: StrAttr | None = None,
78
+ persona: "Callable[[AnyContext], str | None] | str | None" = None,
74
79
  render_persona: bool = False,
75
- system_prompt: StrAttr | None = None,
80
+ system_prompt: "Callable[[AnyContext], str | None] | str | None" = None,
76
81
  render_system_prompt: bool = False,
77
- special_instruction_prompt: StrAttr | None = None,
82
+ special_instruction_prompt: "Callable[[AnyContext], str | None] | str | None" = None,
78
83
  render_special_instruction_prompt: bool = False,
79
84
  workflows: StrListAttr | None = None,
80
85
  render_workflows: bool = True,
81
86
  message: StrAttr | None = None,
82
- attachment: "UserContent | list[UserContent] | Callable[[AnyContext], UserContent | list[UserContent]] | None" = None,
87
+ attachment: "UserContent | list[UserContent] | Callable[[AnyContext], UserContent | list[UserContent]] | None" = None, # noqa
83
88
  render_message: bool = True,
84
89
  tools: (
85
90
  list["ToolOrCallable"] | Callable[[AnyContext], list["ToolOrCallable"]]
@@ -104,19 +109,22 @@ class LLMTask(BaseTask):
104
109
  render_history_file: bool = True,
105
110
  summarize_history: BoolAttr | None = None,
106
111
  render_summarize_history: bool = True,
107
- summarization_prompt: StrAttr | None = None,
112
+ summarization_prompt: "Callable[[AnyContext], str | None] | str | None" = None,
108
113
  render_summarization_prompt: bool = False,
109
114
  history_summarization_token_threshold: IntAttr | None = None,
110
115
  render_history_summarization_token_threshold: bool = True,
111
- rate_limitter: LLMRateLimiter | None = None,
116
+ rate_limitter: LLMRateLimitter | None = None,
112
117
  execute_condition: bool | str | Callable[[AnyContext], bool] = True,
113
118
  retries: int = 2,
114
119
  retry_period: float = 0,
115
- yolo_mode: StrListAttr | BoolAttr | None = None,
116
- render_yolo_mode: bool = True,
117
- readiness_check: (
118
- list[AnyTask] | AnyTask | None | Callable[[AnyContext], None]
120
+ yolo_mode: (
121
+ Callable[[AnyContext], list[str] | bool | None]
122
+ | StrListAttr
123
+ | BoolAttr
124
+ | None
119
125
  ) = None,
126
+ render_yolo_mode: bool = True,
127
+ readiness_check: list[AnyTask] | AnyTask | None = None,
120
128
  readiness_check_delay: float = 0.5,
121
129
  readiness_check_period: float = 5,
122
130
  readiness_failure_threshold: int = 1,
@@ -262,6 +270,7 @@ class LLMTask(BaseTask):
262
270
  conversation_history_attr=self._conversation_history,
263
271
  )
264
272
  inject_conversation_history_notes(conversation_history)
273
+ inject_subagent_conversation_history_into_ctx(ctx, conversation_history)
265
274
  # 2. Get system prompt and user prompt
266
275
  system_prompt, user_prompt = get_system_and_user_prompt(
267
276
  ctx=ctx,
@@ -276,12 +285,29 @@ class LLMTask(BaseTask):
276
285
  render_workflows=self._render_workflows,
277
286
  conversation_history=conversation_history,
278
287
  )
279
- ctx.log_debug(f"SYSTEM PROMPT:\n{system_prompt}")
280
- ctx.log_debug(f"USER PROMPT:\n{user_prompt}")
281
- # 3. Get the agent instance
288
+ # 3. Summarization
289
+ small_model = get_model(
290
+ ctx=ctx,
291
+ model_attr=self._small_model,
292
+ render_model=self._render_small_model,
293
+ model_base_url_attr=self._small_model_base_url,
294
+ render_model_base_url=self._render_small_model_base_url,
295
+ model_api_key_attr=self._small_model_api_key,
296
+ render_model_api_key=self._render_small_model_api_key,
297
+ )
298
+ small_model_settings = get_model_settings(ctx, self._small_model_settings)
299
+ summarization_token_threshold = get_history_summarization_token_threshold(
300
+ ctx,
301
+ self._history_summarization_token_threshold,
302
+ self._render_history_summarization_token_threshold,
303
+ )
304
+ # 4. Get the agent instance
305
+ ctx.log_info(f"SYSTEM PROMPT:\n{system_prompt}")
306
+ ctx.log_info(f"USER PROMPT:\n{user_prompt}")
282
307
  agent = get_agent(
283
308
  ctx=ctx,
284
309
  model=model,
310
+ rate_limitter=self._rate_limitter,
285
311
  system_prompt=system_prompt,
286
312
  model_settings=model_settings,
287
313
  tools_attr=self._tools,
@@ -289,8 +315,14 @@ class LLMTask(BaseTask):
289
315
  toolsets_attr=self._toolsets,
290
316
  additional_toolsets=self._additional_toolsets,
291
317
  yolo_mode=yolo_mode,
318
+ summarization_model=small_model,
319
+ summarization_model_settings=small_model_settings,
320
+ summarization_system_prompt=summarization_prompt,
321
+ summarization_retries=2, # TODO: make this a property
322
+ summarization_token_threshold=summarization_token_threshold,
323
+ history_processors=[], # TODO: make this a property
292
324
  )
293
- # 4. Run the agent iteration and save the results/history
325
+ # 5. Run the agent iteration and save the results/history
294
326
  result = await self._execute_agent(
295
327
  ctx=ctx,
296
328
  agent=agent,
@@ -298,34 +330,10 @@ class LLMTask(BaseTask):
298
330
  attachments=attachments,
299
331
  conversation_history=conversation_history,
300
332
  )
301
- # 5. Summarize
302
- small_model = get_model(
303
- ctx=ctx,
304
- model_attr=self._small_model,
305
- render_model=self._render_small_model,
306
- model_base_url_attr=self._small_model_base_url,
307
- render_model_base_url=self._render_small_model_base_url,
308
- model_api_key_attr=self._small_model_api_key,
309
- render_model_api_key=self._render_small_model_api_key,
310
- )
311
- small_model_settings = get_model_settings(ctx, self._small_model_settings)
312
- conversation_history = await maybe_summarize_history(
313
- ctx=ctx,
314
- conversation_history=conversation_history,
315
- should_summarize_history_attr=self._should_summarize_history,
316
- render_summarize_history=self._render_summarize_history,
317
- history_summarization_token_threshold_attr=(
318
- self._history_summarization_token_threshold
319
- ),
320
- render_history_summarization_token_threshold=(
321
- self._render_history_summarization_token_threshold
322
- ),
323
- model=small_model,
324
- model_settings=small_model_settings,
325
- summarization_prompt=summarization_prompt,
326
- rate_limitter=self._rate_limitter,
327
- )
328
333
  # 6. Write conversation history
334
+ conversation_history.subagent_history = (
335
+ extract_subagent_conversation_history_from_ctx(ctx)
336
+ )
329
337
  await write_conversation_history(
330
338
  ctx=ctx,
331
339
  history_data=conversation_history,
zrb/util/attr.py CHANGED
@@ -10,11 +10,14 @@ from zrb.attr.type import (
10
10
  StrListAttr,
11
11
  )
12
12
  from zrb.context.any_context import AnyContext
13
+ from zrb.context.any_shared_context import AnySharedContext
13
14
  from zrb.util.string.conversion import to_boolean
14
15
 
15
16
 
16
17
  def get_str_list_attr(
17
- ctx: AnyContext, attr: StrListAttr | None, auto_render: bool = True
18
+ ctx: AnyContext | AnySharedContext,
19
+ attr: StrListAttr | None,
20
+ auto_render: bool = True,
18
21
  ) -> list[str]:
19
22
  """
20
23
  Retrieve a list of strings from shared context attributes.
@@ -35,7 +38,9 @@ def get_str_list_attr(
35
38
 
36
39
 
37
40
  def get_str_dict_attr(
38
- ctx: AnyContext, attr: StrDictAttr | None, auto_render: bool = True
41
+ ctx: AnyContext | AnySharedContext,
42
+ attr: StrDictAttr | None,
43
+ auto_render: bool = True,
39
44
  ) -> dict[str, Any]:
40
45
  """
41
46
  Retrieve a dictionary of strings from shared context attributes.
@@ -56,7 +61,7 @@ def get_str_dict_attr(
56
61
 
57
62
 
58
63
  def get_str_attr(
59
- ctx: AnyContext,
64
+ ctx: AnyContext | AnySharedContext,
60
65
  attr: StrAttr | None,
61
66
  default: StrAttr = "",
62
67
  auto_render: bool = True,
@@ -74,13 +79,15 @@ def get_str_attr(
74
79
  str: The string attribute value.
75
80
  """
76
81
  val = get_attr(ctx, attr, default, auto_render)
77
- if not isinstance(val, str):
78
- return str(val)
79
- return val
82
+ if isinstance(val, str):
83
+ return val
84
+ if val is None:
85
+ return ""
86
+ return str(val)
80
87
 
81
88
 
82
89
  def get_bool_attr(
83
- ctx: AnyContext,
90
+ ctx: AnyContext | AnySharedContext,
84
91
  attr: BoolAttr | None,
85
92
  default: BoolAttr = False,
86
93
  auto_render: bool = True,
@@ -106,7 +113,7 @@ def get_bool_attr(
106
113
 
107
114
 
108
115
  def get_int_attr(
109
- ctx: AnyContext,
116
+ ctx: AnyContext | AnySharedContext,
110
117
  attr: IntAttr | None,
111
118
  default: IntAttr = 0,
112
119
  auto_render: bool = True,
@@ -132,7 +139,7 @@ def get_int_attr(
132
139
 
133
140
 
134
141
  def get_float_attr(
135
- ctx: AnyContext,
142
+ ctx: AnyContext | AnySharedContext,
136
143
  attr: FloatAttr | None,
137
144
  default: FloatAttr = 0.0,
138
145
  auto_render: bool = True,
@@ -158,7 +165,7 @@ def get_float_attr(
158
165
 
159
166
 
160
167
  def get_attr(
161
- ctx: AnyContext,
168
+ ctx: AnyContext | AnySharedContext,
162
169
  attr: AnyAttr,
163
170
  default: AnyAttr,
164
171
  auto_render: bool = True,
zrb/util/cli/text.py CHANGED
@@ -11,10 +11,11 @@ def edit_text(
11
11
  editor: str = "vi",
12
12
  extension: str = ".txt",
13
13
  ) -> str:
14
- prompt_message_eol = f"{prompt_message}\n"
15
14
  with tempfile.NamedTemporaryFile(delete=False, suffix=extension) as temp_file:
16
15
  temp_file_name = temp_file.name
17
- temp_file.write(prompt_message_eol.encode())
16
+ if prompt_message.strip() != "":
17
+ prompt_message_eol = f"{prompt_message}\n"
18
+ temp_file.write(prompt_message_eol.encode())
18
19
  # Pre-fill with default content
19
20
  if value:
20
21
  temp_file.write(value.encode())
@@ -22,7 +23,8 @@ def edit_text(
22
23
  subprocess.call([editor, temp_file_name])
23
24
  # Read the edited content
24
25
  edited_content = read_file(temp_file_name)
25
- parts = [text.strip() for text in edited_content.split(prompt_message, 1)]
26
- edited_content = "\n".join(parts).lstrip()
26
+ if prompt_message.strip() != "":
27
+ parts = [text.strip() for text in edited_content.split(prompt_message, 1)]
28
+ edited_content = "\n".join(parts).lstrip()
27
29
  os.remove(temp_file_name)
28
30
  return edited_content
zrb/util/git.py CHANGED
@@ -131,7 +131,7 @@ async def get_branches(
131
131
  Exception: If the git command returns a non-zero exit code.
132
132
  """
133
133
  cmd_result, exit_code = await run_command(
134
- cmd=["git", "rev-parse", "--abbrev-ref", "HEAD"],
134
+ cmd=["git", "branch"],
135
135
  cwd=repo_dir,
136
136
  print_method=print_method,
137
137
  )
@@ -160,7 +160,7 @@ async def delete_branch(
160
160
  Exception: If the git command returns a non-zero exit code.
161
161
  """
162
162
  cmd_result, exit_code = await run_command(
163
- cmd=["git", "branch", "-D", branch_name],
163
+ cmd=["git", "branch", "-d", branch_name],
164
164
  cwd=repo_dir,
165
165
  print_method=print_method,
166
166
  )
zrb/util/yaml.py CHANGED
@@ -34,6 +34,7 @@ def yaml_dump(obj: Any, key: str = "") -> str:
34
34
  allow_unicode=True,
35
35
  sort_keys=False,
36
36
  explicit_end=False,
37
+ width=float("inf"),
37
38
  )
38
39
  if not isinstance(obj_to_dump, (dict, list)):
39
40
  # PyYAML appends '...\n' (document-end) for top-level scalars.
zrb/xcom/xcom.py CHANGED
@@ -34,6 +34,16 @@ class Xcom(deque):
34
34
  else:
35
35
  raise IndexError("Xcom is empty")
36
36
 
37
+ def get(self, default_value: Any = None) -> Any:
38
+ if len(self) > 0:
39
+ return self[0]
40
+ return default_value
41
+
42
+ def set(self, new_value: Any):
43
+ self.push(new_value)
44
+ while len(self) > 1:
45
+ self.pop()
46
+
37
47
  def add_push_callback(self, callback: Callable[[], Any]):
38
48
  if not hasattr(self, "push_callbacks"):
39
49
  self.push_callbacks: list[Callable[[], Any]] = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zrb
3
- Version: 1.21.9
3
+ Version: 1.21.31
4
4
  Summary: Your Automation Powerhouse
5
5
  License: AGPL-3.0-or-later
6
6
  Keywords: Automation,Task Runner,Code Generator,Monorepo,Low Code
@@ -30,22 +30,22 @@ Requires-Dist: black (>=25.11.0,<26.0.0)
30
30
  Requires-Dist: boto3 (>=1.40.14) ; extra == "bedrock"
31
31
  Requires-Dist: chromadb (>=1.3.5,<2.0.0) ; extra == "rag" or extra == "all"
32
32
  Requires-Dist: cohere (>=5.18.0) ; extra == "cohere" or extra == "all"
33
- Requires-Dist: fastapi[standard] (>=0.116.1,<0.117.0)
33
+ Requires-Dist: fastapi[standard] (>=0.123.9,<0.124.0)
34
34
  Requires-Dist: google-auth (>=2.36.0) ; extra == "vertexai" or extra == "all"
35
35
  Requires-Dist: google-genai (>=1.51.0) ; extra == "google" or extra == "all"
36
36
  Requires-Dist: groq (>=0.25.0) ; extra == "groq" or extra == "all"
37
- Requires-Dist: huggingface-hub[inference] (>=0.33.5) ; extra == "huggingface"
37
+ Requires-Dist: huggingface-hub[inference] (>=0.33.5,<1.0.0) ; extra == "huggingface"
38
38
  Requires-Dist: isort (>=7.0.0,<8.0.0)
39
39
  Requires-Dist: libcst (>=1.8.6,<2.0.0)
40
40
  Requires-Dist: markdownify (>=1.2.2,<2.0.0)
41
41
  Requires-Dist: mcp (>1.18.0)
42
42
  Requires-Dist: mistralai (>=1.9.10) ; extra == "mistral"
43
- Requires-Dist: openai (>=2.8.0)
43
+ Requires-Dist: openai (>=2.11.0)
44
44
  Requires-Dist: pdfplumber (>=0.11.7,<0.12.0)
45
45
  Requires-Dist: playwright (>=1.56.0,<2.0.0) ; extra == "playwright" or extra == "all"
46
46
  Requires-Dist: prompt-toolkit (>=3)
47
47
  Requires-Dist: psutil (>=7.0.0,<8.0.0)
48
- Requires-Dist: pydantic-ai-slim (>=1.24.0,<1.25.0)
48
+ Requires-Dist: pydantic-ai-slim (>=1.32.0,<1.33.0)
49
49
  Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
50
50
  Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
51
51
  Requires-Dist: python-jose[cryptography] (>=3.5.0,<4.0.0)