zrb 1.15.3__py3-none-any.whl → 1.21.29__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.
- zrb/__init__.py +2 -6
- zrb/attr/type.py +10 -7
- zrb/builtin/__init__.py +2 -0
- zrb/builtin/git.py +12 -1
- zrb/builtin/group.py +31 -15
- zrb/builtin/llm/attachment.py +40 -0
- zrb/builtin/llm/chat_completion.py +274 -0
- zrb/builtin/llm/chat_session.py +126 -167
- zrb/builtin/llm/chat_session_cmd.py +288 -0
- zrb/builtin/llm/chat_trigger.py +79 -0
- zrb/builtin/llm/history.py +4 -4
- zrb/builtin/llm/llm_ask.py +217 -135
- zrb/builtin/llm/tool/api.py +74 -70
- zrb/builtin/llm/tool/cli.py +35 -21
- zrb/builtin/llm/tool/code.py +55 -73
- zrb/builtin/llm/tool/file.py +278 -344
- zrb/builtin/llm/tool/note.py +84 -0
- zrb/builtin/llm/tool/rag.py +27 -34
- zrb/builtin/llm/tool/sub_agent.py +54 -41
- zrb/builtin/llm/tool/web.py +74 -98
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
- zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
- zrb/builtin/searxng/config/settings.yml +5671 -0
- zrb/builtin/searxng/start.py +21 -0
- zrb/builtin/shell/autocomplete/bash.py +4 -3
- zrb/builtin/shell/autocomplete/zsh.py +4 -3
- zrb/config/config.py +202 -27
- zrb/config/default_prompt/file_extractor_system_prompt.md +109 -9
- zrb/config/default_prompt/interactive_system_prompt.md +24 -30
- zrb/config/default_prompt/persona.md +1 -1
- zrb/config/default_prompt/repo_extractor_system_prompt.md +31 -31
- zrb/config/default_prompt/repo_summarizer_system_prompt.md +27 -8
- zrb/config/default_prompt/summarization_prompt.md +57 -16
- zrb/config/default_prompt/system_prompt.md +36 -30
- zrb/config/llm_config.py +119 -23
- zrb/config/llm_context/config.py +127 -90
- zrb/config/llm_context/config_parser.py +1 -7
- zrb/config/llm_context/workflow.py +81 -0
- zrb/config/llm_rate_limitter.py +100 -47
- zrb/context/any_shared_context.py +7 -1
- zrb/context/context.py +8 -2
- zrb/context/shared_context.py +3 -7
- zrb/group/any_group.py +3 -3
- zrb/group/group.py +3 -3
- zrb/input/any_input.py +5 -1
- zrb/input/base_input.py +18 -6
- zrb/input/option_input.py +13 -1
- zrb/input/text_input.py +7 -24
- zrb/runner/cli.py +21 -20
- zrb/runner/common_util.py +24 -19
- zrb/runner/web_route/task_input_api_route.py +5 -5
- zrb/runner/web_util/user.py +7 -3
- zrb/session/any_session.py +12 -6
- zrb/session/session.py +39 -18
- zrb/task/any_task.py +24 -3
- zrb/task/base/context.py +17 -9
- zrb/task/base/execution.py +15 -8
- zrb/task/base/lifecycle.py +8 -4
- zrb/task/base/monitoring.py +12 -7
- zrb/task/base_task.py +69 -5
- zrb/task/base_trigger.py +12 -5
- zrb/task/llm/agent.py +128 -167
- zrb/task/llm/agent_runner.py +152 -0
- zrb/task/llm/config.py +39 -20
- zrb/task/llm/conversation_history.py +110 -29
- zrb/task/llm/conversation_history_model.py +4 -179
- zrb/task/llm/default_workflow/coding/workflow.md +41 -0
- zrb/task/llm/default_workflow/copywriting/workflow.md +68 -0
- zrb/task/llm/default_workflow/git/workflow.md +118 -0
- zrb/task/llm/default_workflow/golang/workflow.md +128 -0
- zrb/task/llm/default_workflow/html-css/workflow.md +135 -0
- zrb/task/llm/default_workflow/java/workflow.md +146 -0
- zrb/task/llm/default_workflow/javascript/workflow.md +158 -0
- zrb/task/llm/default_workflow/python/workflow.md +160 -0
- zrb/task/llm/default_workflow/researching/workflow.md +153 -0
- zrb/task/llm/default_workflow/rust/workflow.md +162 -0
- zrb/task/llm/default_workflow/shell/workflow.md +299 -0
- zrb/task/llm/file_replacement.py +206 -0
- zrb/task/llm/file_tool_model.py +57 -0
- zrb/task/llm/history_processor.py +206 -0
- zrb/task/llm/history_summarization.py +2 -193
- zrb/task/llm/print_node.py +184 -64
- zrb/task/llm/prompt.py +175 -179
- zrb/task/llm/subagent_conversation_history.py +41 -0
- zrb/task/llm/tool_wrapper.py +226 -85
- zrb/task/llm/workflow.py +76 -0
- zrb/task/llm_task.py +109 -71
- zrb/task/make_task.py +2 -3
- zrb/task/rsync_task.py +25 -10
- zrb/task/scheduler.py +4 -4
- zrb/util/attr.py +54 -39
- zrb/util/cli/markdown.py +12 -0
- zrb/util/cli/text.py +30 -0
- zrb/util/file.py +12 -3
- zrb/util/git.py +2 -2
- zrb/util/{llm/prompt.py → markdown.py} +2 -3
- zrb/util/string/conversion.py +1 -1
- zrb/util/truncate.py +23 -0
- zrb/util/yaml.py +204 -0
- zrb/xcom/xcom.py +10 -0
- {zrb-1.15.3.dist-info → zrb-1.21.29.dist-info}/METADATA +38 -18
- {zrb-1.15.3.dist-info → zrb-1.21.29.dist-info}/RECORD +105 -79
- {zrb-1.15.3.dist-info → zrb-1.21.29.dist-info}/WHEEL +1 -1
- zrb/task/llm/default_workflow/coding.md +0 -24
- zrb/task/llm/default_workflow/copywriting.md +0 -17
- zrb/task/llm/default_workflow/researching.md +0 -18
- {zrb-1.15.3.dist-info → zrb-1.21.29.dist-info}/entry_points.txt +0 -0
zrb/builtin/llm/chat_session.py
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module provides functions for managing interactive chat sessions with an LLM.
|
|
3
|
-
|
|
4
|
-
It handles reading user input, triggering the LLM task, and managing the
|
|
5
|
-
conversation flow via XCom.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
1
|
import asyncio
|
|
9
2
|
import sys
|
|
10
|
-
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from zrb.builtin.llm.chat_session_cmd import (
|
|
6
|
+
ATTACHMENT_CMD,
|
|
7
|
+
HELP_CMD,
|
|
8
|
+
MULTILINE_END_CMD,
|
|
9
|
+
MULTILINE_START_CMD,
|
|
10
|
+
QUIT_CMD,
|
|
11
|
+
RUN_CLI_CMD,
|
|
12
|
+
SAVE_CMD,
|
|
13
|
+
WORKFLOW_CMD,
|
|
14
|
+
YOLO_CMD,
|
|
15
|
+
get_new_attachments,
|
|
16
|
+
get_new_workflows,
|
|
17
|
+
get_new_yolo_mode,
|
|
18
|
+
is_command_match,
|
|
19
|
+
print_commands,
|
|
20
|
+
print_current_attachments,
|
|
21
|
+
print_current_workflows,
|
|
22
|
+
print_current_yolo_mode,
|
|
23
|
+
run_cli_command,
|
|
24
|
+
save_final_result,
|
|
25
|
+
)
|
|
26
|
+
from zrb.builtin.llm.chat_trigger import llm_chat_trigger
|
|
11
27
|
from zrb.config.llm_config import llm_config
|
|
12
28
|
from zrb.context.any_context import AnyContext
|
|
13
|
-
from zrb.util.cli.
|
|
14
|
-
|
|
29
|
+
from zrb.util.cli.markdown import render_markdown
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from asyncio import StreamReader
|
|
33
|
+
|
|
34
|
+
from prompt_toolkit import PromptSession
|
|
15
35
|
|
|
16
36
|
|
|
17
37
|
async def read_user_prompt(ctx: AnyContext) -> str:
|
|
@@ -19,132 +39,97 @@ async def read_user_prompt(ctx: AnyContext) -> str:
|
|
|
19
39
|
Reads user input from the CLI for an interactive chat session.
|
|
20
40
|
Orchestrates the session by calling helper functions.
|
|
21
41
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return final_result
|
|
26
|
-
is_tty = ctx.is_tty
|
|
27
|
-
reader = await _setup_input_reader(is_tty)
|
|
42
|
+
print_commands(ctx)
|
|
43
|
+
is_tty: bool = ctx.is_tty
|
|
44
|
+
reader: PromptSession[Any] | StreamReader = await _setup_input_reader(is_tty)
|
|
28
45
|
multiline_mode = False
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
is_first_time = True
|
|
47
|
+
current_workflows: str = ctx.input.workflows
|
|
48
|
+
current_yolo_mode: bool | str = ctx.input.yolo
|
|
49
|
+
current_attachments: str = ctx.input.attach
|
|
50
|
+
user_inputs: list[str] = []
|
|
51
|
+
final_result: str = ""
|
|
52
|
+
should_end = False
|
|
53
|
+
while not should_end:
|
|
33
54
|
await asyncio.sleep(0.01)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if
|
|
39
|
-
ctx.
|
|
40
|
-
# Handle user input
|
|
41
|
-
if user_input.strip().lower() in ("/bye", "/quit", "/q", "/exit"):
|
|
42
|
-
user_prompt = "\n".join(user_inputs)
|
|
43
|
-
user_inputs = []
|
|
44
|
-
result = await _trigger_ask_and_wait_for_result(
|
|
45
|
-
ctx, user_prompt, current_modes, current_yolo_mode
|
|
46
|
-
)
|
|
47
|
-
if result is not None:
|
|
48
|
-
final_result = result
|
|
49
|
-
break
|
|
50
|
-
elif user_input.strip().lower() in ("/multi",):
|
|
51
|
-
multiline_mode = True
|
|
52
|
-
elif user_input.strip().lower() in ("/end",):
|
|
53
|
-
ctx.print("", plain=True)
|
|
54
|
-
multiline_mode = False
|
|
55
|
-
user_prompt = "\n".join(user_inputs)
|
|
56
|
-
user_inputs = []
|
|
57
|
-
result = await _trigger_ask_and_wait_for_result(
|
|
58
|
-
ctx, user_prompt, current_modes, current_yolo_mode
|
|
59
|
-
)
|
|
60
|
-
if result is not None:
|
|
61
|
-
final_result = result
|
|
62
|
-
elif user_input.strip().lower().startswith("/mode"):
|
|
63
|
-
mode_parts = user_input.split(" ", maxsplit=2)
|
|
64
|
-
if len(mode_parts) > 1:
|
|
65
|
-
current_modes = mode_parts[1]
|
|
66
|
-
ctx.print(f"Current mode: {current_modes}", plain=True)
|
|
67
|
-
ctx.print("", plain=True)
|
|
68
|
-
continue
|
|
69
|
-
elif user_input.strip().lower().startswith("/yolo"):
|
|
70
|
-
yolo_mode_parts = user_input.split(" ", maxsplit=2)
|
|
71
|
-
if len(yolo_mode_parts) > 1:
|
|
72
|
-
current_yolo_mode = to_boolean(yolo_mode_parts[1])
|
|
73
|
-
ctx.print(f"Current_yolo mode: {current_yolo_mode}", plain=True)
|
|
74
|
-
ctx.print("", plain=True)
|
|
75
|
-
continue
|
|
76
|
-
elif user_input.strip().lower() in ("/help", "/info"):
|
|
77
|
-
_show_info(ctx)
|
|
78
|
-
continue
|
|
55
|
+
previous_session_name: str | None = (
|
|
56
|
+
ctx.input.previous_session if is_first_time else ""
|
|
57
|
+
)
|
|
58
|
+
start_new: bool = ctx.input.start_new if is_first_time else False
|
|
59
|
+
if is_first_time and ctx.input.message.strip() != "":
|
|
60
|
+
user_input = ctx.input.message
|
|
79
61
|
else:
|
|
80
|
-
|
|
81
|
-
if multiline_mode:
|
|
62
|
+
# Get user input based on mode
|
|
63
|
+
if not multiline_mode:
|
|
64
|
+
ctx.print("💬 >>", plain=True)
|
|
65
|
+
user_input = await llm_chat_trigger.wait(reader, ctx)
|
|
66
|
+
if not multiline_mode:
|
|
67
|
+
ctx.print("", plain=True)
|
|
68
|
+
# At this point, is_first_time has to be False
|
|
69
|
+
if is_first_time:
|
|
70
|
+
is_first_time = False
|
|
71
|
+
# Handle user input (including slash commands)
|
|
72
|
+
if multiline_mode:
|
|
73
|
+
if is_command_match(user_input, MULTILINE_END_CMD):
|
|
74
|
+
ctx.print("", plain=True)
|
|
75
|
+
multiline_mode = False
|
|
76
|
+
else:
|
|
77
|
+
user_inputs.append(user_input)
|
|
78
|
+
continue
|
|
79
|
+
else:
|
|
80
|
+
if is_command_match(user_input, QUIT_CMD):
|
|
81
|
+
should_end = True
|
|
82
|
+
elif is_command_match(user_input, MULTILINE_START_CMD):
|
|
83
|
+
multiline_mode = True
|
|
84
|
+
ctx.print("", plain=True)
|
|
85
|
+
continue
|
|
86
|
+
elif is_command_match(user_input, WORKFLOW_CMD):
|
|
87
|
+
current_workflows = get_new_workflows(current_workflows, user_input)
|
|
88
|
+
print_current_workflows(ctx, current_workflows)
|
|
89
|
+
continue
|
|
90
|
+
elif is_command_match(user_input, SAVE_CMD):
|
|
91
|
+
save_final_result(ctx, user_input, final_result)
|
|
92
|
+
continue
|
|
93
|
+
elif is_command_match(user_input, ATTACHMENT_CMD):
|
|
94
|
+
current_attachments = get_new_attachments(
|
|
95
|
+
current_attachments, user_input
|
|
96
|
+
)
|
|
97
|
+
print_current_attachments(ctx, current_attachments)
|
|
82
98
|
continue
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
elif is_command_match(user_input, YOLO_CMD):
|
|
100
|
+
current_yolo_mode = get_new_yolo_mode(current_yolo_mode, user_input)
|
|
101
|
+
print_current_yolo_mode(ctx, current_yolo_mode)
|
|
102
|
+
continue
|
|
103
|
+
elif is_command_match(user_input, RUN_CLI_CMD):
|
|
104
|
+
run_cli_command(ctx, user_input)
|
|
105
|
+
continue
|
|
106
|
+
elif is_command_match(user_input, HELP_CMD):
|
|
107
|
+
print_commands(ctx)
|
|
108
|
+
continue
|
|
109
|
+
else:
|
|
110
|
+
user_inputs.append(user_input)
|
|
111
|
+
# Trigger LLM
|
|
112
|
+
user_prompt = "\n".join(user_inputs)
|
|
113
|
+
user_inputs = []
|
|
114
|
+
result = await _trigger_ask_and_wait_for_result(
|
|
115
|
+
ctx=ctx,
|
|
116
|
+
user_prompt=user_prompt,
|
|
117
|
+
attach=current_attachments,
|
|
118
|
+
workflows=current_workflows,
|
|
119
|
+
yolo_mode=current_yolo_mode,
|
|
120
|
+
previous_session_name=previous_session_name,
|
|
121
|
+
start_new=start_new,
|
|
122
|
+
)
|
|
123
|
+
current_attachments = ""
|
|
124
|
+
final_result = final_result if result is None else result
|
|
125
|
+
if ctx.is_web_mode or not is_tty:
|
|
126
|
+
return final_result
|
|
90
127
|
return final_result
|
|
91
128
|
|
|
92
129
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
ctx: The context object for the task.
|
|
98
|
-
"""
|
|
99
|
-
ctx.print(
|
|
100
|
-
"\n".join(
|
|
101
|
-
[
|
|
102
|
-
_show_command("/bye", "Quit from chat session"),
|
|
103
|
-
_show_command("/multi", "Start multiline input"),
|
|
104
|
-
_show_command("/end", "End multiline input"),
|
|
105
|
-
_show_command("/modes", "Show current modes"),
|
|
106
|
-
_show_subcommand("<mode1,mode2,..>", "Set current modes"),
|
|
107
|
-
_show_command("/yolo", "Get current YOLO mode"),
|
|
108
|
-
_show_subcommand("<true|false>", "Set YOLO mode to true/false"),
|
|
109
|
-
_show_command("/help", "Show this message"),
|
|
110
|
-
]
|
|
111
|
-
),
|
|
112
|
-
plain=True,
|
|
113
|
-
)
|
|
114
|
-
ctx.print("", plain=True)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _show_command(command: str, description: str) -> str:
|
|
118
|
-
styled_command = stylize_bold_yellow(command.ljust(25))
|
|
119
|
-
styled_description = stylize_faint(description)
|
|
120
|
-
return f" {styled_command} {styled_description}"
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def _show_subcommand(command: str, description: str) -> str:
|
|
124
|
-
styled_command = stylize_blue(f" {command}".ljust(25))
|
|
125
|
-
styled_description = stylize_faint(description)
|
|
126
|
-
return f" {styled_command} {styled_description}"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
async def _handle_initial_message(ctx: AnyContext) -> str:
|
|
130
|
-
"""Processes the initial message from the command line."""
|
|
131
|
-
if not ctx.input.message or ctx.input.message.strip() == "":
|
|
132
|
-
return ""
|
|
133
|
-
ctx.print("💬 >>", plain=True)
|
|
134
|
-
ctx.print(ctx.input.message, plain=True)
|
|
135
|
-
ctx.print("", plain=True)
|
|
136
|
-
result = await _trigger_ask_and_wait_for_result(
|
|
137
|
-
ctx,
|
|
138
|
-
user_prompt=ctx.input.message,
|
|
139
|
-
modes=ctx.input.modes,
|
|
140
|
-
yolo_mode=ctx.input.yolo,
|
|
141
|
-
previous_session_name=ctx.input.previous_session,
|
|
142
|
-
start_new=ctx.input.start_new,
|
|
143
|
-
)
|
|
144
|
-
return result if result is not None else ""
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
async def _setup_input_reader(is_interactive: bool):
|
|
130
|
+
async def _setup_input_reader(
|
|
131
|
+
is_interactive: bool,
|
|
132
|
+
) -> "PromptSession[Any] | StreamReader":
|
|
148
133
|
"""Sets up and returns the appropriate asynchronous input reader."""
|
|
149
134
|
if is_interactive:
|
|
150
135
|
from prompt_toolkit import PromptSession
|
|
@@ -158,27 +143,12 @@ async def _setup_input_reader(is_interactive: bool):
|
|
|
158
143
|
return reader
|
|
159
144
|
|
|
160
145
|
|
|
161
|
-
async def _read_next_line(reader, ctx: AnyContext) -> str:
|
|
162
|
-
"""Reads one line of input using the provided reader."""
|
|
163
|
-
from prompt_toolkit import PromptSession
|
|
164
|
-
|
|
165
|
-
if isinstance(reader, PromptSession):
|
|
166
|
-
return await reader.prompt_async()
|
|
167
|
-
|
|
168
|
-
line_bytes = await reader.readline()
|
|
169
|
-
if not line_bytes:
|
|
170
|
-
return "/bye" # Signal to exit
|
|
171
|
-
|
|
172
|
-
user_input = line_bytes.decode().strip()
|
|
173
|
-
ctx.print(user_input, plain=True)
|
|
174
|
-
return user_input
|
|
175
|
-
|
|
176
|
-
|
|
177
146
|
async def _trigger_ask_and_wait_for_result(
|
|
178
147
|
ctx: AnyContext,
|
|
179
148
|
user_prompt: str,
|
|
180
|
-
|
|
181
|
-
|
|
149
|
+
attach: str,
|
|
150
|
+
workflows: str,
|
|
151
|
+
yolo_mode: bool | str,
|
|
182
152
|
previous_session_name: str | None = None,
|
|
183
153
|
start_new: bool = False,
|
|
184
154
|
) -> str | None:
|
|
@@ -197,30 +167,16 @@ async def _trigger_ask_and_wait_for_result(
|
|
|
197
167
|
if user_prompt.strip() == "":
|
|
198
168
|
return None
|
|
199
169
|
await _trigger_ask(
|
|
200
|
-
ctx, user_prompt,
|
|
170
|
+
ctx, user_prompt, attach, workflows, yolo_mode, previous_session_name, start_new
|
|
201
171
|
)
|
|
202
172
|
result = await _wait_ask_result(ctx)
|
|
203
|
-
md_result =
|
|
173
|
+
md_result = render_markdown(result) if result is not None else ""
|
|
204
174
|
ctx.print("\n🤖 >>", plain=True)
|
|
205
175
|
ctx.print(md_result, plain=True)
|
|
206
176
|
ctx.print("", plain=True)
|
|
207
177
|
return result
|
|
208
178
|
|
|
209
179
|
|
|
210
|
-
def _render_markdown(markdown_text: str) -> str:
|
|
211
|
-
"""
|
|
212
|
-
Renders Markdown to a string, ensuring link URLs are visible.
|
|
213
|
-
"""
|
|
214
|
-
from rich.console import Console
|
|
215
|
-
from rich.markdown import Markdown
|
|
216
|
-
|
|
217
|
-
console = Console()
|
|
218
|
-
markdown = Markdown(markdown_text, hyperlinks=False)
|
|
219
|
-
with console.capture() as capture:
|
|
220
|
-
console.print(markdown)
|
|
221
|
-
return capture.get()
|
|
222
|
-
|
|
223
|
-
|
|
224
180
|
def get_llm_ask_input_mapping(callback_ctx: AnyContext):
|
|
225
181
|
"""
|
|
226
182
|
Generates the input mapping for the LLM ask task from the callback context.
|
|
@@ -243,7 +199,8 @@ def get_llm_ask_input_mapping(callback_ctx: AnyContext):
|
|
|
243
199
|
"start-new": data.get("start_new"),
|
|
244
200
|
"previous-session": data.get("previous_session_name"),
|
|
245
201
|
"message": data.get("message"),
|
|
246
|
-
"
|
|
202
|
+
"attach": data.get("attach"),
|
|
203
|
+
"workflows": data.get("workflows"),
|
|
247
204
|
"yolo": data.get("yolo"),
|
|
248
205
|
}
|
|
249
206
|
|
|
@@ -251,8 +208,9 @@ def get_llm_ask_input_mapping(callback_ctx: AnyContext):
|
|
|
251
208
|
async def _trigger_ask(
|
|
252
209
|
ctx: AnyContext,
|
|
253
210
|
user_prompt: str,
|
|
254
|
-
|
|
255
|
-
|
|
211
|
+
attach: str,
|
|
212
|
+
workflows: str,
|
|
213
|
+
yolo_mode: bool | str,
|
|
256
214
|
previous_session_name: str | None = None,
|
|
257
215
|
start_new: bool = False,
|
|
258
216
|
):
|
|
@@ -272,7 +230,8 @@ async def _trigger_ask(
|
|
|
272
230
|
"previous_session_name": previous_session_name,
|
|
273
231
|
"start_new": start_new,
|
|
274
232
|
"message": user_prompt,
|
|
275
|
-
"
|
|
233
|
+
"attach": attach,
|
|
234
|
+
"workflows": workflows,
|
|
276
235
|
"yolo": yolo_mode,
|
|
277
236
|
}
|
|
278
237
|
)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from zrb.context.any_context import AnyContext
|
|
5
|
+
from zrb.task.llm.workflow import get_available_workflows
|
|
6
|
+
from zrb.util.cli.markdown import render_markdown
|
|
7
|
+
from zrb.util.cli.style import (
|
|
8
|
+
stylize_blue,
|
|
9
|
+
stylize_bold_yellow,
|
|
10
|
+
stylize_error,
|
|
11
|
+
stylize_faint,
|
|
12
|
+
)
|
|
13
|
+
from zrb.util.file import write_file
|
|
14
|
+
from zrb.util.markdown import make_markdown_section
|
|
15
|
+
from zrb.util.string.conversion import FALSE_STRS, TRUE_STRS, to_boolean
|
|
16
|
+
|
|
17
|
+
MULTILINE_START_CMD = ["/multi", "/multiline"]
|
|
18
|
+
MULTILINE_END_CMD = ["/end"]
|
|
19
|
+
QUIT_CMD = ["/bye", "/quit", "/q", "/exit"]
|
|
20
|
+
WORKFLOW_CMD = ["/workflow", "/workflows", "/skill", "/skills", "/w"]
|
|
21
|
+
SAVE_CMD = ["/save", "/s"]
|
|
22
|
+
ATTACHMENT_CMD = ["/attachment", "/attachments", "/attach"]
|
|
23
|
+
YOLO_CMD = ["/yolo"]
|
|
24
|
+
HELP_CMD = ["/help", "/info"]
|
|
25
|
+
ADD_SUB_CMD = ["add"]
|
|
26
|
+
SET_SUB_CMD = ["set"]
|
|
27
|
+
CLEAR_SUB_CMD = ["clear"]
|
|
28
|
+
RUN_CLI_CMD = ["/run", "/exec", "/execute", "/cmd", "/cli", "!"]
|
|
29
|
+
|
|
30
|
+
# Command display constants
|
|
31
|
+
MULTILINE_START_CMD_DESC = "Start multiline input"
|
|
32
|
+
MULTILINE_END_CMD_DESC = "End multiline input"
|
|
33
|
+
QUIT_CMD_DESC = "Quit from chat session"
|
|
34
|
+
WORKFLOW_CMD_DESC = "Show active workflows"
|
|
35
|
+
WORKFLOW_ADD_SUB_CMD_DESC = (
|
|
36
|
+
"Add active workflow "
|
|
37
|
+
f"(e.g., `{WORKFLOW_CMD[0]} {ADD_SUB_CMD[0]} coding,researching`)"
|
|
38
|
+
)
|
|
39
|
+
WORKFLOW_SET_SUB_CMD_DESC = (
|
|
40
|
+
"Set active workflows " f"(e.g., `{WORKFLOW_CMD[0]} {SET_SUB_CMD[0]} coding,`)"
|
|
41
|
+
)
|
|
42
|
+
WORKFLOW_CLEAR_SUB_CMD_DESC = "Deactivate all workflows"
|
|
43
|
+
SAVE_CMD_DESC = f"Save last response to a file (e.g., `{SAVE_CMD[0]} conclusion.md`)"
|
|
44
|
+
ATTACHMENT_CMD_DESC = "Show current attachment"
|
|
45
|
+
ATTACHMENT_ADD_SUB_CMD_DESC = (
|
|
46
|
+
"Attach a file " f"(e.g., `{ATTACHMENT_CMD[0]} {ADD_SUB_CMD[0]} ./logo.png`)"
|
|
47
|
+
)
|
|
48
|
+
ATTACHMENT_SET_SUB_CMD_DESC = (
|
|
49
|
+
"Set attachments "
|
|
50
|
+
f"(e.g., `{ATTACHMENT_CMD[0]} {SET_SUB_CMD[0]} ./logo.png,./diagram.png`)"
|
|
51
|
+
)
|
|
52
|
+
ATTACHMENT_CLEAR_SUB_CMD_DESC = "Clear attachment"
|
|
53
|
+
YOLO_CMD_DESC = "Show/manipulate current YOLO mode"
|
|
54
|
+
YOLO_SET_CMD_DESC = (
|
|
55
|
+
"Assign YOLO tools "
|
|
56
|
+
f"(e.g., `{YOLO_CMD[0]} {SET_SUB_CMD[0]} read_from_file,analyze_file`)"
|
|
57
|
+
)
|
|
58
|
+
YOLO_SET_TRUE_CMD_DESC = "Activate YOLO mode for all tools"
|
|
59
|
+
YOLO_SET_FALSE_CMD_DESC = "Deactivate YOLO mode for all tools"
|
|
60
|
+
RUN_CLI_CMD_DESC = "Run a non-interactive CLI command"
|
|
61
|
+
HELP_CMD_DESC = "Show info/help"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def print_current_yolo_mode(
|
|
65
|
+
ctx: AnyContext, current_yolo_mode_value: str | bool
|
|
66
|
+
) -> None:
|
|
67
|
+
yolo_mode_str = (
|
|
68
|
+
current_yolo_mode_value if current_yolo_mode_value != "" else "*Not Set*"
|
|
69
|
+
)
|
|
70
|
+
ctx.print(render_markdown(f"🎲 Current YOLO mode: {yolo_mode_str}"), plain=True)
|
|
71
|
+
ctx.print("", plain=True)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def print_current_attachments(ctx: AnyContext, current_attachments_value: str) -> None:
|
|
75
|
+
attachments_str = (
|
|
76
|
+
current_attachments_value if current_attachments_value != "" else "*Not Set*"
|
|
77
|
+
)
|
|
78
|
+
ctx.print(render_markdown(f"📎 Current attachments: {attachments_str}"), plain=True)
|
|
79
|
+
ctx.print("", plain=True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def print_current_workflows(ctx: AnyContext, current_workflows_value: str) -> None:
|
|
83
|
+
available_workflows = get_available_workflows()
|
|
84
|
+
available_workflows_str = (
|
|
85
|
+
", ".join(sorted([workflow_name for workflow_name in available_workflows]))
|
|
86
|
+
if len(available_workflows) > 0
|
|
87
|
+
else "*No Available Workflow*"
|
|
88
|
+
)
|
|
89
|
+
current_workflows_str = (
|
|
90
|
+
current_workflows_value
|
|
91
|
+
if current_workflows_value != ""
|
|
92
|
+
else "*No Active Workflow*"
|
|
93
|
+
)
|
|
94
|
+
ctx.print(
|
|
95
|
+
render_markdown(
|
|
96
|
+
"\n".join(
|
|
97
|
+
[
|
|
98
|
+
f"- 🔄 Current workflows : {current_workflows_str}",
|
|
99
|
+
f"- 📚 Available workflows : {available_workflows_str}",
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
),
|
|
103
|
+
plain=True,
|
|
104
|
+
)
|
|
105
|
+
ctx.print("", plain=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def save_final_result(ctx: AnyContext, user_input: str, final_result: str) -> None:
|
|
109
|
+
save_path = get_command_param(user_input, SAVE_CMD)
|
|
110
|
+
save_path = os.path.expanduser(save_path)
|
|
111
|
+
if os.path.exists(save_path):
|
|
112
|
+
ctx.print(
|
|
113
|
+
stylize_error(f"Cannot save to existing file: {save_path}"),
|
|
114
|
+
plain=True,
|
|
115
|
+
)
|
|
116
|
+
return
|
|
117
|
+
write_file(save_path, final_result)
|
|
118
|
+
ctx.print(f"Response saved to {save_path}", plain=True)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def run_cli_command(ctx: AnyContext, user_input: str) -> None:
|
|
122
|
+
command = get_command_param(user_input, RUN_CLI_CMD)
|
|
123
|
+
result = subprocess.run(
|
|
124
|
+
command,
|
|
125
|
+
shell=True,
|
|
126
|
+
capture_output=True,
|
|
127
|
+
text=True,
|
|
128
|
+
)
|
|
129
|
+
ctx.print(
|
|
130
|
+
render_markdown(
|
|
131
|
+
make_markdown_section(
|
|
132
|
+
f"`{command}`",
|
|
133
|
+
"\n".join(
|
|
134
|
+
[
|
|
135
|
+
make_markdown_section("📤 Stdout", result.stdout, as_code=True),
|
|
136
|
+
make_markdown_section("🚫 Stderr", result.stderr, as_code=True),
|
|
137
|
+
make_markdown_section(
|
|
138
|
+
"🎯 Return code", f"Return Code: {result.returncode}"
|
|
139
|
+
),
|
|
140
|
+
]
|
|
141
|
+
),
|
|
142
|
+
)
|
|
143
|
+
),
|
|
144
|
+
plain=True,
|
|
145
|
+
)
|
|
146
|
+
ctx.print("", plain=True)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def get_new_yolo_mode(old_yolo_mode: str | bool, user_input: str) -> str | bool:
|
|
150
|
+
new_yolo_mode = get_command_param(user_input, YOLO_CMD)
|
|
151
|
+
if new_yolo_mode != "":
|
|
152
|
+
if new_yolo_mode in TRUE_STRS or new_yolo_mode in FALSE_STRS:
|
|
153
|
+
return to_boolean(new_yolo_mode)
|
|
154
|
+
return new_yolo_mode
|
|
155
|
+
if isinstance(old_yolo_mode, bool):
|
|
156
|
+
return old_yolo_mode
|
|
157
|
+
return _normalize_comma_separated_str(old_yolo_mode)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_new_attachments(old_attachment: str, user_input: str) -> str:
|
|
161
|
+
if not is_command_match(user_input, ATTACHMENT_CMD):
|
|
162
|
+
return _normalize_comma_separated_str(old_attachment)
|
|
163
|
+
if is_command_match(user_input, ATTACHMENT_CMD, SET_SUB_CMD):
|
|
164
|
+
return _normalize_comma_separated_str(
|
|
165
|
+
get_command_param(user_input, ATTACHMENT_CMD, SET_SUB_CMD)
|
|
166
|
+
)
|
|
167
|
+
if is_command_match(user_input, ATTACHMENT_CMD, CLEAR_SUB_CMD):
|
|
168
|
+
return ""
|
|
169
|
+
if is_command_match(user_input, ATTACHMENT_CMD, ADD_SUB_CMD):
|
|
170
|
+
new_attachment = get_command_param(user_input, ATTACHMENT_CMD, ADD_SUB_CMD)
|
|
171
|
+
return _normalize_comma_separated_str(
|
|
172
|
+
",".join([old_attachment, new_attachment])
|
|
173
|
+
)
|
|
174
|
+
return old_attachment
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_new_workflows(old_workflow: str, user_input: str) -> str:
|
|
178
|
+
if not is_command_match(user_input, WORKFLOW_CMD):
|
|
179
|
+
return _normalize_comma_separated_str(old_workflow)
|
|
180
|
+
if is_command_match(user_input, WORKFLOW_CMD, SET_SUB_CMD):
|
|
181
|
+
return _normalize_comma_separated_str(
|
|
182
|
+
get_command_param(user_input, WORKFLOW_CMD, SET_SUB_CMD)
|
|
183
|
+
)
|
|
184
|
+
if is_command_match(user_input, WORKFLOW_CMD, CLEAR_SUB_CMD):
|
|
185
|
+
return ""
|
|
186
|
+
if is_command_match(user_input, WORKFLOW_CMD, ADD_SUB_CMD):
|
|
187
|
+
new_workflow = get_command_param(user_input, WORKFLOW_CMD, ADD_SUB_CMD)
|
|
188
|
+
return _normalize_comma_separated_str(",".join([old_workflow, new_workflow]))
|
|
189
|
+
return _normalize_comma_separated_str(old_workflow)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _normalize_comma_separated_str(comma_separated_str: str) -> str:
|
|
193
|
+
return ",".join(
|
|
194
|
+
[
|
|
195
|
+
workflow_name.strip()
|
|
196
|
+
for workflow_name in comma_separated_str.split(",")
|
|
197
|
+
if workflow_name.strip() != ""
|
|
198
|
+
]
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def get_command_param(user_input: str, *cmd_patterns: list[str]) -> str:
|
|
203
|
+
if not is_command_match(user_input, *cmd_patterns):
|
|
204
|
+
return ""
|
|
205
|
+
parts = [part for part in user_input.split(" ") if part.strip() != ""]
|
|
206
|
+
if len(parts) <= len(cmd_patterns):
|
|
207
|
+
return ""
|
|
208
|
+
params = parts[len(cmd_patterns) :]
|
|
209
|
+
return " ".join(params)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def is_command_match(user_input: str, *cmd_patterns: list[str]) -> bool:
|
|
213
|
+
parts = [part for part in user_input.split(" ") if part.strip() != ""]
|
|
214
|
+
if len(cmd_patterns) > len(parts):
|
|
215
|
+
return False
|
|
216
|
+
for index, cmd_pattern in enumerate(cmd_patterns):
|
|
217
|
+
part = parts[index]
|
|
218
|
+
if part.lower() not in cmd_pattern:
|
|
219
|
+
return False
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def print_commands(ctx: AnyContext):
|
|
224
|
+
"""
|
|
225
|
+
Displays the available chat session commands to the user.
|
|
226
|
+
Args:
|
|
227
|
+
ctx: The context object for the task.
|
|
228
|
+
"""
|
|
229
|
+
ctx.print(
|
|
230
|
+
"\n".join(
|
|
231
|
+
[
|
|
232
|
+
_show_command(QUIT_CMD[0], QUIT_CMD_DESC),
|
|
233
|
+
_show_command(MULTILINE_START_CMD[0], MULTILINE_START_CMD_DESC),
|
|
234
|
+
_show_command(MULTILINE_END_CMD[0], MULTILINE_END_CMD_DESC),
|
|
235
|
+
_show_command(ATTACHMENT_CMD[0], ATTACHMENT_CMD_DESC),
|
|
236
|
+
_show_subcommand(
|
|
237
|
+
ADD_SUB_CMD[0], "<file-path>", ATTACHMENT_ADD_SUB_CMD_DESC
|
|
238
|
+
),
|
|
239
|
+
_show_subcommand(
|
|
240
|
+
SET_SUB_CMD[0],
|
|
241
|
+
"<file1-path,file2-path,...>",
|
|
242
|
+
ATTACHMENT_SET_SUB_CMD_DESC,
|
|
243
|
+
),
|
|
244
|
+
_show_subcommand(CLEAR_SUB_CMD[0], "", ATTACHMENT_CLEAR_SUB_CMD_DESC),
|
|
245
|
+
_show_command(WORKFLOW_CMD[0], WORKFLOW_CMD_DESC),
|
|
246
|
+
_show_subcommand(
|
|
247
|
+
ADD_SUB_CMD[0], "<workflow>", WORKFLOW_ADD_SUB_CMD_DESC
|
|
248
|
+
),
|
|
249
|
+
_show_subcommand(
|
|
250
|
+
SET_SUB_CMD[0],
|
|
251
|
+
"<workflow1,workflow2,..>",
|
|
252
|
+
WORKFLOW_SET_SUB_CMD_DESC,
|
|
253
|
+
),
|
|
254
|
+
_show_subcommand(CLEAR_SUB_CMD[0], "", WORKFLOW_CLEAR_SUB_CMD_DESC),
|
|
255
|
+
_show_command(f"{SAVE_CMD[0]}", SAVE_CMD_DESC),
|
|
256
|
+
_show_command(YOLO_CMD[0], YOLO_CMD_DESC),
|
|
257
|
+
_show_subcommand(SET_SUB_CMD[0], "true", YOLO_SET_TRUE_CMD_DESC),
|
|
258
|
+
_show_subcommand(SET_SUB_CMD[0], "false", YOLO_SET_FALSE_CMD_DESC),
|
|
259
|
+
_show_subcommand(
|
|
260
|
+
SET_SUB_CMD[0], "<tool1,tool2,tool2>", YOLO_SET_CMD_DESC
|
|
261
|
+
),
|
|
262
|
+
_show_command(RUN_CLI_CMD[0], ""),
|
|
263
|
+
_show_command_param("<cli-command>", RUN_CLI_CMD_DESC),
|
|
264
|
+
_show_command(HELP_CMD[0], HELP_CMD_DESC),
|
|
265
|
+
]
|
|
266
|
+
),
|
|
267
|
+
plain=True,
|
|
268
|
+
)
|
|
269
|
+
ctx.print("", plain=True)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _show_command(command: str, description: str) -> str:
|
|
273
|
+
styled_command = stylize_bold_yellow(command.ljust(37))
|
|
274
|
+
styled_description = stylize_faint(description)
|
|
275
|
+
return f" {styled_command} {styled_description}"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _show_subcommand(subcommand: str, param: str, description: str) -> str:
|
|
279
|
+
styled_subcommand = stylize_bold_yellow(f" {subcommand}")
|
|
280
|
+
styled_param = stylize_blue(param.ljust(32 - len(subcommand)))
|
|
281
|
+
styled_description = stylize_faint(description)
|
|
282
|
+
return f" {styled_subcommand} {styled_param} {styled_description}"
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _show_command_param(param: str, description: str) -> str:
|
|
286
|
+
styled_param = stylize_blue(f" {param}".ljust(37))
|
|
287
|
+
styled_description = stylize_faint(description)
|
|
288
|
+
return f" {styled_param} {styled_description}"
|