zrb 1.21.29__py3-none-any.whl → 2.0.0a4__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 +118 -129
- zrb/builtin/__init__.py +54 -2
- zrb/builtin/llm/chat.py +147 -0
- zrb/callback/callback.py +8 -1
- zrb/cmd/cmd_result.py +2 -1
- zrb/config/config.py +491 -280
- zrb/config/helper.py +84 -0
- zrb/config/web_auth_config.py +50 -35
- zrb/context/any_shared_context.py +13 -2
- zrb/context/context.py +31 -3
- zrb/context/print_fn.py +13 -0
- zrb/context/shared_context.py +14 -1
- zrb/input/option_input.py +30 -2
- zrb/llm/agent/__init__.py +9 -0
- zrb/llm/agent/agent.py +215 -0
- zrb/llm/agent/summarizer.py +20 -0
- zrb/llm/app/__init__.py +10 -0
- zrb/llm/app/completion.py +281 -0
- zrb/llm/app/confirmation/allow_tool.py +66 -0
- zrb/llm/app/confirmation/handler.py +178 -0
- zrb/llm/app/confirmation/replace_confirmation.py +77 -0
- zrb/llm/app/keybinding.py +34 -0
- zrb/llm/app/layout.py +117 -0
- zrb/llm/app/lexer.py +155 -0
- zrb/llm/app/redirection.py +28 -0
- zrb/llm/app/style.py +16 -0
- zrb/llm/app/ui.py +733 -0
- zrb/llm/config/__init__.py +4 -0
- zrb/llm/config/config.py +122 -0
- zrb/llm/config/limiter.py +247 -0
- zrb/llm/history_manager/__init__.py +4 -0
- zrb/llm/history_manager/any_history_manager.py +23 -0
- zrb/llm/history_manager/file_history_manager.py +91 -0
- zrb/llm/history_processor/summarizer.py +108 -0
- zrb/llm/note/__init__.py +3 -0
- zrb/llm/note/manager.py +122 -0
- zrb/llm/prompt/__init__.py +29 -0
- zrb/llm/prompt/claude_compatibility.py +92 -0
- zrb/llm/prompt/compose.py +55 -0
- zrb/llm/prompt/default.py +51 -0
- zrb/llm/prompt/markdown/mandate.md +23 -0
- zrb/llm/prompt/markdown/persona.md +3 -0
- zrb/llm/prompt/markdown/summarizer.md +21 -0
- zrb/llm/prompt/note.py +41 -0
- zrb/llm/prompt/system_context.py +46 -0
- zrb/llm/prompt/zrb.py +41 -0
- zrb/llm/skill/__init__.py +3 -0
- zrb/llm/skill/manager.py +86 -0
- zrb/llm/task/__init__.py +4 -0
- zrb/llm/task/llm_chat_task.py +316 -0
- zrb/llm/task/llm_task.py +245 -0
- zrb/llm/tool/__init__.py +39 -0
- zrb/llm/tool/bash.py +75 -0
- zrb/llm/tool/code.py +266 -0
- zrb/llm/tool/file.py +419 -0
- zrb/llm/tool/note.py +70 -0
- zrb/{builtin/llm → llm}/tool/rag.py +8 -5
- zrb/llm/tool/search/brave.py +53 -0
- zrb/llm/tool/search/searxng.py +47 -0
- zrb/llm/tool/search/serpapi.py +47 -0
- zrb/llm/tool/skill.py +19 -0
- zrb/llm/tool/sub_agent.py +70 -0
- zrb/llm/tool/web.py +97 -0
- zrb/llm/tool/zrb_task.py +66 -0
- zrb/llm/util/attachment.py +101 -0
- zrb/llm/util/prompt.py +104 -0
- zrb/llm/util/stream_response.py +178 -0
- zrb/session/any_session.py +0 -3
- zrb/session/session.py +1 -1
- zrb/task/base/context.py +25 -13
- zrb/task/base/execution.py +52 -47
- zrb/task/base/lifecycle.py +7 -4
- zrb/task/base_task.py +48 -49
- zrb/task/base_trigger.py +4 -1
- zrb/task/cmd_task.py +6 -0
- zrb/task/http_check.py +11 -5
- zrb/task/make_task.py +3 -0
- zrb/task/rsync_task.py +5 -0
- zrb/task/scaffolder.py +7 -4
- zrb/task/scheduler.py +3 -0
- zrb/task/tcp_check.py +6 -4
- zrb/util/ascii_art/art/bee.txt +17 -0
- zrb/util/ascii_art/art/cat.txt +9 -0
- zrb/util/ascii_art/art/ghost.txt +16 -0
- zrb/util/ascii_art/art/panda.txt +17 -0
- zrb/util/ascii_art/art/rose.txt +14 -0
- zrb/util/ascii_art/art/unicorn.txt +15 -0
- zrb/util/ascii_art/banner.py +92 -0
- zrb/util/cli/markdown.py +22 -2
- zrb/util/cmd/command.py +33 -10
- zrb/util/file.py +51 -32
- zrb/util/match.py +78 -0
- zrb/util/run.py +3 -3
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/METADATA +9 -15
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/RECORD +100 -128
- zrb/attr/__init__.py +0 -0
- zrb/builtin/llm/attachment.py +0 -40
- zrb/builtin/llm/chat_completion.py +0 -274
- zrb/builtin/llm/chat_session.py +0 -270
- zrb/builtin/llm/chat_session_cmd.py +0 -288
- zrb/builtin/llm/chat_trigger.py +0 -79
- zrb/builtin/llm/history.py +0 -71
- zrb/builtin/llm/input.py +0 -27
- zrb/builtin/llm/llm_ask.py +0 -269
- zrb/builtin/llm/previous-session.js +0 -21
- zrb/builtin/llm/tool/__init__.py +0 -0
- zrb/builtin/llm/tool/api.py +0 -75
- zrb/builtin/llm/tool/cli.py +0 -52
- zrb/builtin/llm/tool/code.py +0 -236
- zrb/builtin/llm/tool/file.py +0 -560
- zrb/builtin/llm/tool/note.py +0 -84
- zrb/builtin/llm/tool/sub_agent.py +0 -150
- zrb/builtin/llm/tool/web.py +0 -171
- zrb/builtin/project/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/__init__.py +0 -0
- zrb/builtin/project/create/__init__.py +0 -0
- zrb/builtin/shell/__init__.py +0 -0
- zrb/builtin/shell/autocomplete/__init__.py +0 -0
- zrb/callback/__init__.py +0 -0
- zrb/cmd/__init__.py +0 -0
- zrb/config/default_prompt/interactive_system_prompt.md +0 -29
- zrb/config/default_prompt/persona.md +0 -1
- zrb/config/default_prompt/summarization_prompt.md +0 -57
- zrb/config/default_prompt/system_prompt.md +0 -38
- zrb/config/llm_config.py +0 -339
- zrb/config/llm_context/config.py +0 -166
- zrb/config/llm_context/config_parser.py +0 -40
- zrb/config/llm_context/workflow.py +0 -81
- zrb/config/llm_rate_limitter.py +0 -190
- zrb/content_transformer/__init__.py +0 -0
- zrb/context/__init__.py +0 -0
- zrb/dot_dict/__init__.py +0 -0
- zrb/env/__init__.py +0 -0
- zrb/group/__init__.py +0 -0
- zrb/input/__init__.py +0 -0
- zrb/runner/__init__.py +0 -0
- zrb/runner/web_route/__init__.py +0 -0
- zrb/runner/web_route/home_page/__init__.py +0 -0
- zrb/session/__init__.py +0 -0
- zrb/session_state_log/__init__.py +0 -0
- zrb/session_state_logger/__init__.py +0 -0
- zrb/task/__init__.py +0 -0
- zrb/task/base/__init__.py +0 -0
- zrb/task/llm/__init__.py +0 -0
- zrb/task/llm/agent.py +0 -204
- zrb/task/llm/agent_runner.py +0 -152
- zrb/task/llm/config.py +0 -122
- zrb/task/llm/conversation_history.py +0 -209
- zrb/task/llm/conversation_history_model.py +0 -67
- zrb/task/llm/default_workflow/coding/workflow.md +0 -41
- zrb/task/llm/default_workflow/copywriting/workflow.md +0 -68
- zrb/task/llm/default_workflow/git/workflow.md +0 -118
- zrb/task/llm/default_workflow/golang/workflow.md +0 -128
- zrb/task/llm/default_workflow/html-css/workflow.md +0 -135
- zrb/task/llm/default_workflow/java/workflow.md +0 -146
- zrb/task/llm/default_workflow/javascript/workflow.md +0 -158
- zrb/task/llm/default_workflow/python/workflow.md +0 -160
- zrb/task/llm/default_workflow/researching/workflow.md +0 -153
- zrb/task/llm/default_workflow/rust/workflow.md +0 -162
- zrb/task/llm/default_workflow/shell/workflow.md +0 -299
- zrb/task/llm/error.py +0 -95
- zrb/task/llm/file_replacement.py +0 -206
- zrb/task/llm/file_tool_model.py +0 -57
- zrb/task/llm/history_processor.py +0 -206
- zrb/task/llm/history_summarization.py +0 -25
- zrb/task/llm/print_node.py +0 -221
- zrb/task/llm/prompt.py +0 -321
- zrb/task/llm/subagent_conversation_history.py +0 -41
- zrb/task/llm/tool_wrapper.py +0 -361
- zrb/task/llm/typing.py +0 -3
- zrb/task/llm/workflow.py +0 -76
- zrb/task/llm_task.py +0 -379
- zrb/task_status/__init__.py +0 -0
- zrb/util/__init__.py +0 -0
- zrb/util/cli/__init__.py +0 -0
- zrb/util/cmd/__init__.py +0 -0
- zrb/util/codemod/__init__.py +0 -0
- zrb/util/string/__init__.py +0 -0
- zrb/xcom/__init__.py +0 -0
- /zrb/{config/default_prompt/file_extractor_system_prompt.md → llm/prompt/markdown/file_extractor.md} +0 -0
- /zrb/{config/default_prompt/repo_extractor_system_prompt.md → llm/prompt/markdown/repo_extractor.md} +0 -0
- /zrb/{config/default_prompt/repo_summarizer_system_prompt.md → llm/prompt/markdown/repo_summarizer.md} +0 -0
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +0 -0
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/entry_points.txt +0 -0
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "A workflow for writing robust, safe, and maintainable shell scripts."
|
|
3
|
-
---
|
|
4
|
-
Follow this workflow to create reliable, secure, and well-structured shell scripts.
|
|
5
|
-
|
|
6
|
-
# Core Mandates
|
|
7
|
-
|
|
8
|
-
- **Safety First:** Prevent common shell scripting pitfalls and security issues
|
|
9
|
-
- **Robustness:** Handle errors gracefully and predictably
|
|
10
|
-
- **Portability:** Write scripts that work across different environments
|
|
11
|
-
- **Maintainability:** Create readable, well-documented scripts
|
|
12
|
-
|
|
13
|
-
# Tool Usage Guideline
|
|
14
|
-
- Use `read_from_file` to analyze existing shell scripts and configurations
|
|
15
|
-
- Use `run_shell_command` to test shell scripts and commands
|
|
16
|
-
- Use `write_to_file` to create new shell scripts
|
|
17
|
-
- Use `search_files` to find shell patterns and conventions
|
|
18
|
-
|
|
19
|
-
# Step 1: Script Foundation
|
|
20
|
-
|
|
21
|
-
## Non-Negotiable Boilerplate
|
|
22
|
-
Every script MUST start with this header. No exceptions.
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
#!/usr/bin/env bash
|
|
26
|
-
|
|
27
|
-
# Exit on error, exit on unset variables, and exit on pipe fails.
|
|
28
|
-
set -euo pipefail
|
|
29
|
-
|
|
30
|
-
# Set IFS to default and save for restoration
|
|
31
|
-
OLD_IFS="$IFS"
|
|
32
|
-
IFS=$'\n\t'
|
|
33
|
-
|
|
34
|
-
# Ensure proper cleanup on exit
|
|
35
|
-
trap 'IFS="$OLD_IFS"' EXIT
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Additional Safety Options
|
|
39
|
-
```bash
|
|
40
|
-
# Treat unset variables as errors
|
|
41
|
-
set -u
|
|
42
|
-
|
|
43
|
-
# Exit on any error
|
|
44
|
-
set -e
|
|
45
|
-
|
|
46
|
-
# Exit on pipe failure
|
|
47
|
-
set -o pipefail
|
|
48
|
-
|
|
49
|
-
# Make sure to fail on command substitution
|
|
50
|
-
shopt -s inherit_errexit
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
# Step 2: Script Structure Planning
|
|
54
|
-
|
|
55
|
-
1. **Define Purpose:** Clearly understand what the script should accomplish
|
|
56
|
-
2. **Identify Dependencies:** Determine required commands and tools
|
|
57
|
-
3. **Plan Error Handling:** Design comprehensive error handling strategy
|
|
58
|
-
4. **Structure Functions:** Plan modular function organization
|
|
59
|
-
5. **Consider Portability:** Ensure compatibility with target environments
|
|
60
|
-
|
|
61
|
-
# Step 3: Implementation Standards
|
|
62
|
-
|
|
63
|
-
## Variable Safety
|
|
64
|
-
- **Quote Everything:** Always use `"$variable"` instead of `$variable`
|
|
65
|
-
- **Local Variables:** Use `local` in functions to avoid global scope pollution
|
|
66
|
-
- **Constants:** Use `readonly` for values that shouldn't change
|
|
67
|
-
- **Arrays:** Use arrays for lists instead of strings with spaces
|
|
68
|
-
|
|
69
|
-
## Function Definitions
|
|
70
|
-
```bash
|
|
71
|
-
# Standard function template
|
|
72
|
-
function my_function() {
|
|
73
|
-
local arg1="$1"
|
|
74
|
-
local arg2="$2"
|
|
75
|
-
|
|
76
|
-
# Function logic here
|
|
77
|
-
echo "Processing: $arg1, $arg2"
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Error Handling Patterns
|
|
82
|
-
```bash
|
|
83
|
-
# Check command existence
|
|
84
|
-
if ! command -v required_command &> /dev/null; then
|
|
85
|
-
echo "Error: required_command not found" >&2
|
|
86
|
-
exit 1
|
|
87
|
-
fi
|
|
88
|
-
|
|
89
|
-
# Handle command failures
|
|
90
|
-
important_command || {
|
|
91
|
-
echo "Error: important_command failed" >&2
|
|
92
|
-
exit 1
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
# Temporary file handling
|
|
96
|
-
tmp_file=$(mktemp)
|
|
97
|
-
trap 'rm -f "$tmp_file"' EXIT
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
# Step 4: Write Script Components
|
|
101
|
-
|
|
102
|
-
## Standard Script Structure
|
|
103
|
-
```bash
|
|
104
|
-
#!/usr/bin/env bash
|
|
105
|
-
set -euo pipefail
|
|
106
|
-
|
|
107
|
-
# Configuration section
|
|
108
|
-
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
109
|
-
readonly SCRIPT_NAME="$(basename "$0")"
|
|
110
|
-
|
|
111
|
-
# Function definitions
|
|
112
|
-
function usage() {
|
|
113
|
-
cat << EOF
|
|
114
|
-
Usage: $SCRIPT_NAME [OPTIONS]
|
|
115
|
-
|
|
116
|
-
Description of what the script does.
|
|
117
|
-
|
|
118
|
-
OPTIONS:
|
|
119
|
-
-h, --help Show this help message
|
|
120
|
-
-v, --verbose Enable verbose output
|
|
121
|
-
-f, --file FILE Specify input file
|
|
122
|
-
|
|
123
|
-
EXAMPLES:
|
|
124
|
-
$SCRIPT_NAME -f input.txt
|
|
125
|
-
$SCRIPT_NAME --verbose
|
|
126
|
-
|
|
127
|
-
EOF
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function log() {
|
|
131
|
-
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function error() {
|
|
135
|
-
echo "[ERROR] $*" >&2
|
|
136
|
-
exit 1
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function main() {
|
|
140
|
-
local verbose=false
|
|
141
|
-
local input_file=""
|
|
142
|
-
|
|
143
|
-
# Parse command line arguments
|
|
144
|
-
while [[ $# -gt 0 ]]; do
|
|
145
|
-
case $1 in
|
|
146
|
-
-h|--help)
|
|
147
|
-
usage
|
|
148
|
-
exit 0
|
|
149
|
-
;;
|
|
150
|
-
-v|--verbose)
|
|
151
|
-
verbose=true
|
|
152
|
-
;;
|
|
153
|
-
-f|--file)
|
|
154
|
-
input_file="$2"
|
|
155
|
-
shift
|
|
156
|
-
;;
|
|
157
|
-
*)
|
|
158
|
-
error "Unknown option: $1"
|
|
159
|
-
;;
|
|
160
|
-
esac
|
|
161
|
-
shift
|
|
162
|
-
done
|
|
163
|
-
|
|
164
|
-
# Main script logic
|
|
165
|
-
if [[ -z "$input_file" ]]; then
|
|
166
|
-
error "Input file is required"
|
|
167
|
-
fi
|
|
168
|
-
|
|
169
|
-
if [[ ! -f "$input_file" ]]; then
|
|
170
|
-
error "File not found: $input_file"
|
|
171
|
-
fi
|
|
172
|
-
|
|
173
|
-
"$verbose" && log "Processing file: $input_file"
|
|
174
|
-
|
|
175
|
-
# Actual work here
|
|
176
|
-
process_file "$input_file"
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
# Helper functions
|
|
180
|
-
function process_file() {
|
|
181
|
-
local file="$1"
|
|
182
|
-
# File processing logic
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
# Main execution
|
|
186
|
-
main "$@"
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
# Step 5: Testing and Verification
|
|
190
|
-
|
|
191
|
-
1. **ShellCheck Validation:** Run `shellcheck script.sh` and fix all warnings
|
|
192
|
-
2. **Syntax Checking:** Run `bash -n script.sh` to check syntax
|
|
193
|
-
3. **Dry Run Testing:** Test with sample data and edge cases
|
|
194
|
-
4. **Error Scenario Testing:** Verify error handling works correctly
|
|
195
|
-
5. **Portability Testing:** Test on different shell versions if needed
|
|
196
|
-
|
|
197
|
-
# Step 6: Quality Assurance
|
|
198
|
-
|
|
199
|
-
## ShellCheck Compliance
|
|
200
|
-
- **No Warnings:** Address all ShellCheck warnings
|
|
201
|
-
- **Best Practices:** Follow ShellCheck recommendations
|
|
202
|
-
- **Security:** Eliminate potential security issues
|
|
203
|
-
|
|
204
|
-
## Code Review Checklist
|
|
205
|
-
- [ ] Script starts with proper shebang and safety options
|
|
206
|
-
- [ ] All variables are properly quoted
|
|
207
|
-
- [ ] Error handling is comprehensive
|
|
208
|
-
- [ ] Functions use `local` variables
|
|
209
|
-
- [ ] Temporary files are cleaned up properly
|
|
210
|
-
- [ ] Command existence is verified
|
|
211
|
-
- [ ] Exit codes are used appropriately
|
|
212
|
-
- [ ] Documentation is complete
|
|
213
|
-
|
|
214
|
-
# Step 7: Advanced Patterns
|
|
215
|
-
|
|
216
|
-
## Signal Handling
|
|
217
|
-
```bash
|
|
218
|
-
# Handle interrupts gracefully
|
|
219
|
-
function cleanup() {
|
|
220
|
-
echo "Cleaning up..."
|
|
221
|
-
# Cleanup logic
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
trap cleanup EXIT INT TERM
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
## Parallel Execution
|
|
228
|
-
```bash
|
|
229
|
-
# Run commands in parallel with proper error handling
|
|
230
|
-
function run_parallel() {
|
|
231
|
-
local pids=()
|
|
232
|
-
|
|
233
|
-
for item in "${items[@]}"; do
|
|
234
|
-
process_item "$item" &
|
|
235
|
-
pids+=($!)
|
|
236
|
-
done
|
|
237
|
-
|
|
238
|
-
# Wait for all processes
|
|
239
|
-
for pid in "${pids[@]}"; do
|
|
240
|
-
wait "$pid"
|
|
241
|
-
done
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Configuration Management
|
|
246
|
-
```bash
|
|
247
|
-
# Load configuration from file
|
|
248
|
-
function load_config() {
|
|
249
|
-
local config_file="${1:-config.sh}"
|
|
250
|
-
|
|
251
|
-
if [[ -f "$config_file" ]]; then
|
|
252
|
-
# shellcheck source=/dev/null
|
|
253
|
-
source "$config_file"
|
|
254
|
-
else
|
|
255
|
-
error "Configuration file not found: $config_file"
|
|
256
|
-
fi
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
# Step 8: Finalize and Deliver
|
|
261
|
-
|
|
262
|
-
1. **Final Testing:** Run comprehensive test scenarios
|
|
263
|
-
2. **Documentation:** Ensure usage instructions are clear
|
|
264
|
-
3. **Security Review:** Verify no security vulnerabilities
|
|
265
|
-
4. **Performance Check:** Ensure script runs efficiently
|
|
266
|
-
5. **Deployment Ready:** Confirm script is ready for production use
|
|
267
|
-
|
|
268
|
-
# Common Commands Reference
|
|
269
|
-
|
|
270
|
-
## Development
|
|
271
|
-
- `shellcheck script.sh`: Lint shell script
|
|
272
|
-
- `bash -n script.sh`: Check syntax without executing
|
|
273
|
-
- `bash -x script.sh`: Debug with execution tracing
|
|
274
|
-
- `time bash script.sh`: Measure execution time
|
|
275
|
-
|
|
276
|
-
## Testing
|
|
277
|
-
- Create test cases with known inputs and expected outputs
|
|
278
|
-
- Test edge cases and error conditions
|
|
279
|
-
- Verify portability across different environments
|
|
280
|
-
- Test with different shell versions if required
|
|
281
|
-
|
|
282
|
-
# Risk Assessment Guidelines
|
|
283
|
-
|
|
284
|
-
## Low Risk (Proceed Directly)
|
|
285
|
-
- Creating new utility scripts with proper safety measures
|
|
286
|
-
- Adding functions to existing well-structured scripts
|
|
287
|
-
- Running linters and syntax checkers
|
|
288
|
-
|
|
289
|
-
## Moderate Risk (Explain and Confirm)
|
|
290
|
-
- Modifying existing production scripts
|
|
291
|
-
- Scripts that modify system files
|
|
292
|
-
- Operations requiring elevated privileges
|
|
293
|
-
- Changes to critical system automation
|
|
294
|
-
|
|
295
|
-
## High Risk (Refuse and Explain)
|
|
296
|
-
- Scripts that could cause data loss
|
|
297
|
-
- Operations on system-critical paths
|
|
298
|
-
- Changes to security-sensitive scripts
|
|
299
|
-
- Scripts with potential for infinite loops or resource exhaustion
|
zrb/task/llm/error.py
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import TYPE_CHECKING, Any, Optional
|
|
3
|
-
|
|
4
|
-
if TYPE_CHECKING:
|
|
5
|
-
from openai import APIError
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# Define a structured error model for tool execution failures
|
|
9
|
-
class ToolExecutionError:
|
|
10
|
-
def __init__(
|
|
11
|
-
self,
|
|
12
|
-
tool_name: str,
|
|
13
|
-
error_type: str,
|
|
14
|
-
message: str,
|
|
15
|
-
details: Optional[str] = None,
|
|
16
|
-
):
|
|
17
|
-
self.tool_name = tool_name
|
|
18
|
-
self.error_type = error_type
|
|
19
|
-
self.message = message
|
|
20
|
-
self.details = details
|
|
21
|
-
|
|
22
|
-
def to_dict(self) -> dict[str, Any]:
|
|
23
|
-
return {
|
|
24
|
-
"tool_name": self.tool_name,
|
|
25
|
-
"error_type": self.error_type,
|
|
26
|
-
"message": self.message,
|
|
27
|
-
"details": self.details,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
def model_dump_json(self, indent: int = 2) -> str:
|
|
31
|
-
return json.dumps(self.to_dict(), indent=indent)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def extract_api_error_details(error: "APIError") -> str:
|
|
35
|
-
"""Extract detailed error information from an APIError."""
|
|
36
|
-
details = f"{error.message}"
|
|
37
|
-
# Try to parse the error body as JSON
|
|
38
|
-
if error.body:
|
|
39
|
-
try:
|
|
40
|
-
if isinstance(error.body, str):
|
|
41
|
-
body_json = json.loads(error.body)
|
|
42
|
-
elif isinstance(error.body, bytes):
|
|
43
|
-
body_json = json.loads(error.body.decode("utf-8"))
|
|
44
|
-
else:
|
|
45
|
-
body_json = error.body
|
|
46
|
-
# Extract error details from the JSON structure
|
|
47
|
-
if isinstance(body_json, dict):
|
|
48
|
-
if "error" in body_json:
|
|
49
|
-
error_obj = body_json["error"]
|
|
50
|
-
if isinstance(error_obj, dict):
|
|
51
|
-
if "message" in error_obj:
|
|
52
|
-
details += f"\nProvider message: {error_obj['message']}"
|
|
53
|
-
if "code" in error_obj:
|
|
54
|
-
details += f"\nError code: {error_obj['code']}"
|
|
55
|
-
if "status" in error_obj:
|
|
56
|
-
details += f"\nStatus: {error_obj['status']}"
|
|
57
|
-
# Check for metadata that might contain provider-specific information
|
|
58
|
-
if "metadata" in body_json and isinstance(body_json["metadata"], dict):
|
|
59
|
-
metadata = body_json["metadata"]
|
|
60
|
-
if "provider_name" in metadata:
|
|
61
|
-
details += f"\nProvider: {metadata['provider_name']}"
|
|
62
|
-
if "raw" in metadata:
|
|
63
|
-
try:
|
|
64
|
-
raw_json = json.loads(metadata["raw"])
|
|
65
|
-
if "error" in raw_json and isinstance(
|
|
66
|
-
raw_json["error"], dict
|
|
67
|
-
):
|
|
68
|
-
raw_error = raw_json["error"]
|
|
69
|
-
if "message" in raw_error:
|
|
70
|
-
details += (
|
|
71
|
-
f"\nRaw error message: {raw_error['message']}"
|
|
72
|
-
)
|
|
73
|
-
except (KeyError, TypeError, ValueError):
|
|
74
|
-
# If we can't parse the raw JSON, just include it as is
|
|
75
|
-
details += f"\nRaw error data: {metadata['raw']}"
|
|
76
|
-
except json.JSONDecodeError:
|
|
77
|
-
# If we can't parse the JSON, include the raw body
|
|
78
|
-
details += f"\nRaw error body: {error.body}"
|
|
79
|
-
except Exception as e:
|
|
80
|
-
# Catch any other exceptions during parsing
|
|
81
|
-
details += f"\nError parsing error body: {str(e)}"
|
|
82
|
-
# Include request information if available
|
|
83
|
-
if hasattr(error, "request") and error.request:
|
|
84
|
-
if hasattr(error.request, "method") and hasattr(error.request, "url"):
|
|
85
|
-
details += f"\nRequest: {error.request.method} {error.request.url}"
|
|
86
|
-
# Include a truncated version of the request content if available
|
|
87
|
-
if hasattr(error.request, "content") and error.request.content:
|
|
88
|
-
content = error.request.content
|
|
89
|
-
if isinstance(content, bytes):
|
|
90
|
-
try:
|
|
91
|
-
content = content.decode("utf-8")
|
|
92
|
-
except UnicodeDecodeError:
|
|
93
|
-
content = str(content)
|
|
94
|
-
details += f"\nRequest content: {content}"
|
|
95
|
-
return details
|
zrb/task/llm/file_replacement.py
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import difflib
|
|
2
|
-
import os
|
|
3
|
-
import shlex
|
|
4
|
-
import subprocess
|
|
5
|
-
import tempfile
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from zrb.config.config import CFG
|
|
9
|
-
from zrb.task.llm.file_tool_model import FileReplacement
|
|
10
|
-
from zrb.util.file import read_file
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def is_single_path_replacement(param: Any):
|
|
14
|
-
if isinstance(param, dict):
|
|
15
|
-
return _dict_has_exact_keys(
|
|
16
|
-
param, {"path", "old_text", "new_text"}
|
|
17
|
-
) or _dict_has_exact_keys(param, {"path", "old_text", "new_text", "count"})
|
|
18
|
-
if isinstance(param, list):
|
|
19
|
-
current_path = None
|
|
20
|
-
for single_replacement in param:
|
|
21
|
-
if not is_single_path_replacement(single_replacement):
|
|
22
|
-
return False
|
|
23
|
-
if current_path is not None and current_path != single_replacement["path"]:
|
|
24
|
-
return False
|
|
25
|
-
current_path = single_replacement["path"]
|
|
26
|
-
return True
|
|
27
|
-
return False
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _dict_has_exact_keys(dictionary: dict, required_keys: set) -> bool:
|
|
31
|
-
"""
|
|
32
|
-
Check if a dictionary contains exactly the specified keys.
|
|
33
|
-
More efficient for large dictionaries.
|
|
34
|
-
"""
|
|
35
|
-
if len(dictionary) != len(required_keys):
|
|
36
|
-
return False
|
|
37
|
-
return all(key in dictionary for key in required_keys)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def edit_replacement(
|
|
41
|
-
replacement: list[FileReplacement] | FileReplacement,
|
|
42
|
-
diff_edit_command_tpl: str | None = None,
|
|
43
|
-
) -> tuple[list[FileReplacement] | FileReplacement, bool]:
|
|
44
|
-
# Normalize input to list
|
|
45
|
-
replacement_list = [replacement] if isinstance(replacement, dict) else replacement
|
|
46
|
-
if not replacement_list:
|
|
47
|
-
return replacement, False
|
|
48
|
-
path = replacement_list[0]["path"]
|
|
49
|
-
original_content = read_file(path)
|
|
50
|
-
# Calculate initial proposed content based on AI's suggestion
|
|
51
|
-
proposed_content = _apply_initial_replacements(original_content, replacement_list)
|
|
52
|
-
# Open external editor for user modification
|
|
53
|
-
edited_content = _open_diff_editor(
|
|
54
|
-
path, original_content, proposed_content, diff_edit_command_tpl
|
|
55
|
-
)
|
|
56
|
-
# If content hasn't changed from proposal, return original replacement
|
|
57
|
-
if edited_content == proposed_content:
|
|
58
|
-
return replacement, False
|
|
59
|
-
# Calculate optimized replacements based on user's final edit
|
|
60
|
-
optimized_replacements = _generate_optimized_replacements(
|
|
61
|
-
path, original_content, edited_content
|
|
62
|
-
)
|
|
63
|
-
return optimized_replacements, True
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _apply_initial_replacements(
|
|
67
|
-
content: str, replacement_list: list[FileReplacement]
|
|
68
|
-
) -> str:
|
|
69
|
-
new_content = content
|
|
70
|
-
for single_replacement in replacement_list:
|
|
71
|
-
old_text = single_replacement["old_text"]
|
|
72
|
-
new_text = single_replacement["new_text"]
|
|
73
|
-
count = single_replacement.get("count", -1)
|
|
74
|
-
new_content = new_content.replace(old_text, new_text, count)
|
|
75
|
-
return new_content
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _open_diff_editor(
|
|
79
|
-
original_path: str,
|
|
80
|
-
original_content: str,
|
|
81
|
-
proposed_content: str,
|
|
82
|
-
diff_edit_command_tpl: str | None,
|
|
83
|
-
) -> str:
|
|
84
|
-
if diff_edit_command_tpl is None:
|
|
85
|
-
diff_edit_command_tpl = CFG.DEFAULT_DIFF_EDIT_COMMAND_TPL
|
|
86
|
-
_, extension = os.path.splitext(original_path)
|
|
87
|
-
with tempfile.NamedTemporaryFile(delete=False, suffix=extension) as old_file:
|
|
88
|
-
old_file_name = old_file.name
|
|
89
|
-
old_file.write(original_content.encode())
|
|
90
|
-
old_file.flush()
|
|
91
|
-
with tempfile.NamedTemporaryFile(delete=False, suffix=extension) as new_file:
|
|
92
|
-
new_file_name = new_file.name
|
|
93
|
-
new_file.write(proposed_content.encode())
|
|
94
|
-
new_file.flush()
|
|
95
|
-
diff_edit_command = diff_edit_command_tpl.format(
|
|
96
|
-
old=old_file_name, new=new_file_name
|
|
97
|
-
)
|
|
98
|
-
subprocess.call(shlex.split(diff_edit_command))
|
|
99
|
-
edited_content = read_file(new_file_name)
|
|
100
|
-
if os.path.exists(old_file_name):
|
|
101
|
-
os.remove(old_file_name)
|
|
102
|
-
if os.path.exists(new_file_name):
|
|
103
|
-
os.remove(new_file_name)
|
|
104
|
-
return edited_content
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _generate_optimized_replacements(
|
|
108
|
-
path: str, original_content: str, edited_content: str
|
|
109
|
-
) -> list[FileReplacement]:
|
|
110
|
-
matcher = difflib.SequenceMatcher(None, original_content, edited_content)
|
|
111
|
-
hunks = _group_opcodes_into_hunks(matcher.get_opcodes())
|
|
112
|
-
replacements = []
|
|
113
|
-
for hunk in hunks:
|
|
114
|
-
replacement = _create_replacement_from_hunk(
|
|
115
|
-
path, original_content, edited_content, hunk
|
|
116
|
-
)
|
|
117
|
-
if replacement:
|
|
118
|
-
replacements.append(replacement)
|
|
119
|
-
return replacements
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _group_opcodes_into_hunks(opcodes, merge_threshold=200):
|
|
123
|
-
"""
|
|
124
|
-
Groups opcodes into hunks.
|
|
125
|
-
'equal' blocks smaller than merge_threshold are treated as context (glue) within a hunk.
|
|
126
|
-
"""
|
|
127
|
-
hunks = []
|
|
128
|
-
current_hunk = []
|
|
129
|
-
for tag, i1, i2, j1, j2 in opcodes:
|
|
130
|
-
if tag == "equal":
|
|
131
|
-
if i2 - i1 < merge_threshold:
|
|
132
|
-
if current_hunk:
|
|
133
|
-
current_hunk.append((tag, i1, i2, j1, j2))
|
|
134
|
-
else:
|
|
135
|
-
if current_hunk:
|
|
136
|
-
hunks.append(current_hunk)
|
|
137
|
-
current_hunk = []
|
|
138
|
-
else:
|
|
139
|
-
current_hunk.append((tag, i1, i2, j1, j2))
|
|
140
|
-
if current_hunk:
|
|
141
|
-
hunks.append(current_hunk)
|
|
142
|
-
return hunks
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _create_replacement_from_hunk(
|
|
146
|
-
path: str, original_content: str, edited_content: str, hunk: list
|
|
147
|
-
) -> FileReplacement | None:
|
|
148
|
-
# Trim leading/trailing 'equal' opcodes
|
|
149
|
-
while hunk and hunk[0][0] == "equal":
|
|
150
|
-
hunk.pop(0)
|
|
151
|
-
while hunk and hunk[-1][0] == "equal":
|
|
152
|
-
hunk.pop()
|
|
153
|
-
if not hunk:
|
|
154
|
-
return None
|
|
155
|
-
# Determine range of modification
|
|
156
|
-
i_start = hunk[0][1]
|
|
157
|
-
i_end = hunk[-1][2]
|
|
158
|
-
j_start = hunk[0][3]
|
|
159
|
-
j_end = hunk[-1][4]
|
|
160
|
-
base_old_text = original_content[i_start:i_end]
|
|
161
|
-
base_new_text = edited_content[j_start:j_end]
|
|
162
|
-
if base_old_text == base_new_text:
|
|
163
|
-
return None
|
|
164
|
-
# Expand context
|
|
165
|
-
start, end = _expand_context_for_uniqueness(original_content, i_start, i_end)
|
|
166
|
-
start, end = _expand_to_word_boundary(original_content, start, end)
|
|
167
|
-
final_old_text = original_content[start:end]
|
|
168
|
-
# Reconstruct new text
|
|
169
|
-
prefix = original_content[start:i_start]
|
|
170
|
-
suffix = original_content[i_end:end]
|
|
171
|
-
final_new_text = prefix + base_new_text + suffix
|
|
172
|
-
if final_old_text == final_new_text:
|
|
173
|
-
return None
|
|
174
|
-
return {
|
|
175
|
-
"path": path,
|
|
176
|
-
"old_text": final_old_text,
|
|
177
|
-
"new_text": final_new_text,
|
|
178
|
-
"count": 1,
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def _expand_context_for_uniqueness(
|
|
183
|
-
content: str, start: int, end: int
|
|
184
|
-
) -> tuple[int, int]:
|
|
185
|
-
"""Expands the range [start, end] until the substring content[start:end] is unique."""
|
|
186
|
-
while content.count(content[start:end]) > 1:
|
|
187
|
-
if start == 0 and end == len(content):
|
|
188
|
-
break
|
|
189
|
-
if start > 0:
|
|
190
|
-
start -= 1
|
|
191
|
-
if end < len(content):
|
|
192
|
-
end += 1
|
|
193
|
-
return start, end
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def _expand_to_word_boundary(content: str, start: int, end: int) -> tuple[int, int]:
|
|
197
|
-
"""Expands the range [start, end] outwards to the nearest whitespace boundaries."""
|
|
198
|
-
|
|
199
|
-
def is_boundary(char):
|
|
200
|
-
return char.isspace()
|
|
201
|
-
|
|
202
|
-
while start > 0 and not is_boundary(content[start - 1]):
|
|
203
|
-
start -= 1
|
|
204
|
-
while end < len(content) and not is_boundary(content[end]):
|
|
205
|
-
end += 1
|
|
206
|
-
return start, end
|
zrb/task/llm/file_tool_model.py
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from typing import Literal
|
|
3
|
-
|
|
4
|
-
if sys.version_info >= (3, 12):
|
|
5
|
-
from typing import NotRequired, TypedDict
|
|
6
|
-
else:
|
|
7
|
-
from typing_extensions import NotRequired, TypedDict
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class FileToRead(TypedDict):
|
|
11
|
-
"""
|
|
12
|
-
Configuration for reading a file or file section.
|
|
13
|
-
|
|
14
|
-
Attributes:
|
|
15
|
-
path (str): Absolute or relative path to the file
|
|
16
|
-
start_line (int | None): Starting line number (1-based, inclusive).
|
|
17
|
-
If None, reads from beginning.
|
|
18
|
-
end_line (int | None): Ending line number (1-based, exclusive). If None, reads to end.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
path: str
|
|
22
|
-
start_line: NotRequired[int | None]
|
|
23
|
-
end_line: NotRequired[int | None]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class FileToWrite(TypedDict):
|
|
27
|
-
"""
|
|
28
|
-
Configuration for writing content to a file.
|
|
29
|
-
|
|
30
|
-
Attributes:
|
|
31
|
-
path (str): Absolute or relative path where file will be written.
|
|
32
|
-
content (str): Content to write. CRITICAL: For JSON, ensure all special characters
|
|
33
|
-
in this string are properly escaped.
|
|
34
|
-
mode (str): Mode for writing:
|
|
35
|
-
'w' (overwrite, default), 'a' (append), 'x' (create exclusively).
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
path: str
|
|
39
|
-
content: str
|
|
40
|
-
mode: NotRequired[Literal["w", "wt", "tw", "a", "at", "ta", "x", "xt", "tx"]]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class FileReplacement(TypedDict):
|
|
44
|
-
"""
|
|
45
|
-
Configuration for a single text replacement operation in a file.
|
|
46
|
-
|
|
47
|
-
Attributes:
|
|
48
|
-
path (str): Absolute or relative path to the file
|
|
49
|
-
old_text (str): Exact text to find and replace (must match file content exactly)
|
|
50
|
-
new_text (str): New text to replace with
|
|
51
|
-
count (int): Optional. Number of occurrences to replace. Defaults to -1 (all).
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
path: str
|
|
55
|
-
old_text: str
|
|
56
|
-
new_text: str
|
|
57
|
-
count: NotRequired[int]
|