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.

Files changed (192) hide show
  1. zrb/__init__.py +118 -129
  2. zrb/builtin/__init__.py +54 -2
  3. zrb/builtin/llm/chat.py +147 -0
  4. zrb/callback/callback.py +8 -1
  5. zrb/cmd/cmd_result.py +2 -1
  6. zrb/config/config.py +491 -280
  7. zrb/config/helper.py +84 -0
  8. zrb/config/web_auth_config.py +50 -35
  9. zrb/context/any_shared_context.py +13 -2
  10. zrb/context/context.py +31 -3
  11. zrb/context/print_fn.py +13 -0
  12. zrb/context/shared_context.py +14 -1
  13. zrb/input/option_input.py +30 -2
  14. zrb/llm/agent/__init__.py +9 -0
  15. zrb/llm/agent/agent.py +215 -0
  16. zrb/llm/agent/summarizer.py +20 -0
  17. zrb/llm/app/__init__.py +10 -0
  18. zrb/llm/app/completion.py +281 -0
  19. zrb/llm/app/confirmation/allow_tool.py +66 -0
  20. zrb/llm/app/confirmation/handler.py +178 -0
  21. zrb/llm/app/confirmation/replace_confirmation.py +77 -0
  22. zrb/llm/app/keybinding.py +34 -0
  23. zrb/llm/app/layout.py +117 -0
  24. zrb/llm/app/lexer.py +155 -0
  25. zrb/llm/app/redirection.py +28 -0
  26. zrb/llm/app/style.py +16 -0
  27. zrb/llm/app/ui.py +733 -0
  28. zrb/llm/config/__init__.py +4 -0
  29. zrb/llm/config/config.py +122 -0
  30. zrb/llm/config/limiter.py +247 -0
  31. zrb/llm/history_manager/__init__.py +4 -0
  32. zrb/llm/history_manager/any_history_manager.py +23 -0
  33. zrb/llm/history_manager/file_history_manager.py +91 -0
  34. zrb/llm/history_processor/summarizer.py +108 -0
  35. zrb/llm/note/__init__.py +3 -0
  36. zrb/llm/note/manager.py +122 -0
  37. zrb/llm/prompt/__init__.py +29 -0
  38. zrb/llm/prompt/claude_compatibility.py +92 -0
  39. zrb/llm/prompt/compose.py +55 -0
  40. zrb/llm/prompt/default.py +51 -0
  41. zrb/llm/prompt/markdown/mandate.md +23 -0
  42. zrb/llm/prompt/markdown/persona.md +3 -0
  43. zrb/llm/prompt/markdown/summarizer.md +21 -0
  44. zrb/llm/prompt/note.py +41 -0
  45. zrb/llm/prompt/system_context.py +46 -0
  46. zrb/llm/prompt/zrb.py +41 -0
  47. zrb/llm/skill/__init__.py +3 -0
  48. zrb/llm/skill/manager.py +86 -0
  49. zrb/llm/task/__init__.py +4 -0
  50. zrb/llm/task/llm_chat_task.py +316 -0
  51. zrb/llm/task/llm_task.py +245 -0
  52. zrb/llm/tool/__init__.py +39 -0
  53. zrb/llm/tool/bash.py +75 -0
  54. zrb/llm/tool/code.py +266 -0
  55. zrb/llm/tool/file.py +419 -0
  56. zrb/llm/tool/note.py +70 -0
  57. zrb/{builtin/llm → llm}/tool/rag.py +8 -5
  58. zrb/llm/tool/search/brave.py +53 -0
  59. zrb/llm/tool/search/searxng.py +47 -0
  60. zrb/llm/tool/search/serpapi.py +47 -0
  61. zrb/llm/tool/skill.py +19 -0
  62. zrb/llm/tool/sub_agent.py +70 -0
  63. zrb/llm/tool/web.py +97 -0
  64. zrb/llm/tool/zrb_task.py +66 -0
  65. zrb/llm/util/attachment.py +101 -0
  66. zrb/llm/util/prompt.py +104 -0
  67. zrb/llm/util/stream_response.py +178 -0
  68. zrb/session/any_session.py +0 -3
  69. zrb/session/session.py +1 -1
  70. zrb/task/base/context.py +25 -13
  71. zrb/task/base/execution.py +52 -47
  72. zrb/task/base/lifecycle.py +7 -4
  73. zrb/task/base_task.py +48 -49
  74. zrb/task/base_trigger.py +4 -1
  75. zrb/task/cmd_task.py +6 -0
  76. zrb/task/http_check.py +11 -5
  77. zrb/task/make_task.py +3 -0
  78. zrb/task/rsync_task.py +5 -0
  79. zrb/task/scaffolder.py +7 -4
  80. zrb/task/scheduler.py +3 -0
  81. zrb/task/tcp_check.py +6 -4
  82. zrb/util/ascii_art/art/bee.txt +17 -0
  83. zrb/util/ascii_art/art/cat.txt +9 -0
  84. zrb/util/ascii_art/art/ghost.txt +16 -0
  85. zrb/util/ascii_art/art/panda.txt +17 -0
  86. zrb/util/ascii_art/art/rose.txt +14 -0
  87. zrb/util/ascii_art/art/unicorn.txt +15 -0
  88. zrb/util/ascii_art/banner.py +92 -0
  89. zrb/util/cli/markdown.py +22 -2
  90. zrb/util/cmd/command.py +33 -10
  91. zrb/util/file.py +51 -32
  92. zrb/util/match.py +78 -0
  93. zrb/util/run.py +3 -3
  94. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/METADATA +9 -15
  95. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/RECORD +100 -128
  96. zrb/attr/__init__.py +0 -0
  97. zrb/builtin/llm/attachment.py +0 -40
  98. zrb/builtin/llm/chat_completion.py +0 -274
  99. zrb/builtin/llm/chat_session.py +0 -270
  100. zrb/builtin/llm/chat_session_cmd.py +0 -288
  101. zrb/builtin/llm/chat_trigger.py +0 -79
  102. zrb/builtin/llm/history.py +0 -71
  103. zrb/builtin/llm/input.py +0 -27
  104. zrb/builtin/llm/llm_ask.py +0 -269
  105. zrb/builtin/llm/previous-session.js +0 -21
  106. zrb/builtin/llm/tool/__init__.py +0 -0
  107. zrb/builtin/llm/tool/api.py +0 -75
  108. zrb/builtin/llm/tool/cli.py +0 -52
  109. zrb/builtin/llm/tool/code.py +0 -236
  110. zrb/builtin/llm/tool/file.py +0 -560
  111. zrb/builtin/llm/tool/note.py +0 -84
  112. zrb/builtin/llm/tool/sub_agent.py +0 -150
  113. zrb/builtin/llm/tool/web.py +0 -171
  114. zrb/builtin/project/__init__.py +0 -0
  115. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/__init__.py +0 -0
  116. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/service/__init__.py +0 -0
  117. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/__init__.py +0 -0
  118. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/__init__.py +0 -0
  119. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/__init__.py +0 -0
  120. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/__init__.py +0 -0
  121. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/__init__.py +0 -0
  122. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/__init__.py +0 -0
  123. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/__init__.py +0 -0
  124. zrb/builtin/project/create/__init__.py +0 -0
  125. zrb/builtin/shell/__init__.py +0 -0
  126. zrb/builtin/shell/autocomplete/__init__.py +0 -0
  127. zrb/callback/__init__.py +0 -0
  128. zrb/cmd/__init__.py +0 -0
  129. zrb/config/default_prompt/interactive_system_prompt.md +0 -29
  130. zrb/config/default_prompt/persona.md +0 -1
  131. zrb/config/default_prompt/summarization_prompt.md +0 -57
  132. zrb/config/default_prompt/system_prompt.md +0 -38
  133. zrb/config/llm_config.py +0 -339
  134. zrb/config/llm_context/config.py +0 -166
  135. zrb/config/llm_context/config_parser.py +0 -40
  136. zrb/config/llm_context/workflow.py +0 -81
  137. zrb/config/llm_rate_limitter.py +0 -190
  138. zrb/content_transformer/__init__.py +0 -0
  139. zrb/context/__init__.py +0 -0
  140. zrb/dot_dict/__init__.py +0 -0
  141. zrb/env/__init__.py +0 -0
  142. zrb/group/__init__.py +0 -0
  143. zrb/input/__init__.py +0 -0
  144. zrb/runner/__init__.py +0 -0
  145. zrb/runner/web_route/__init__.py +0 -0
  146. zrb/runner/web_route/home_page/__init__.py +0 -0
  147. zrb/session/__init__.py +0 -0
  148. zrb/session_state_log/__init__.py +0 -0
  149. zrb/session_state_logger/__init__.py +0 -0
  150. zrb/task/__init__.py +0 -0
  151. zrb/task/base/__init__.py +0 -0
  152. zrb/task/llm/__init__.py +0 -0
  153. zrb/task/llm/agent.py +0 -204
  154. zrb/task/llm/agent_runner.py +0 -152
  155. zrb/task/llm/config.py +0 -122
  156. zrb/task/llm/conversation_history.py +0 -209
  157. zrb/task/llm/conversation_history_model.py +0 -67
  158. zrb/task/llm/default_workflow/coding/workflow.md +0 -41
  159. zrb/task/llm/default_workflow/copywriting/workflow.md +0 -68
  160. zrb/task/llm/default_workflow/git/workflow.md +0 -118
  161. zrb/task/llm/default_workflow/golang/workflow.md +0 -128
  162. zrb/task/llm/default_workflow/html-css/workflow.md +0 -135
  163. zrb/task/llm/default_workflow/java/workflow.md +0 -146
  164. zrb/task/llm/default_workflow/javascript/workflow.md +0 -158
  165. zrb/task/llm/default_workflow/python/workflow.md +0 -160
  166. zrb/task/llm/default_workflow/researching/workflow.md +0 -153
  167. zrb/task/llm/default_workflow/rust/workflow.md +0 -162
  168. zrb/task/llm/default_workflow/shell/workflow.md +0 -299
  169. zrb/task/llm/error.py +0 -95
  170. zrb/task/llm/file_replacement.py +0 -206
  171. zrb/task/llm/file_tool_model.py +0 -57
  172. zrb/task/llm/history_processor.py +0 -206
  173. zrb/task/llm/history_summarization.py +0 -25
  174. zrb/task/llm/print_node.py +0 -221
  175. zrb/task/llm/prompt.py +0 -321
  176. zrb/task/llm/subagent_conversation_history.py +0 -41
  177. zrb/task/llm/tool_wrapper.py +0 -361
  178. zrb/task/llm/typing.py +0 -3
  179. zrb/task/llm/workflow.py +0 -76
  180. zrb/task/llm_task.py +0 -379
  181. zrb/task_status/__init__.py +0 -0
  182. zrb/util/__init__.py +0 -0
  183. zrb/util/cli/__init__.py +0 -0
  184. zrb/util/cmd/__init__.py +0 -0
  185. zrb/util/codemod/__init__.py +0 -0
  186. zrb/util/string/__init__.py +0 -0
  187. zrb/xcom/__init__.py +0 -0
  188. /zrb/{config/default_prompt/file_extractor_system_prompt.md → llm/prompt/markdown/file_extractor.md} +0 -0
  189. /zrb/{config/default_prompt/repo_extractor_system_prompt.md → llm/prompt/markdown/repo_extractor.md} +0 -0
  190. /zrb/{config/default_prompt/repo_summarizer_system_prompt.md → llm/prompt/markdown/repo_summarizer.md} +0 -0
  191. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +0 -0
  192. {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
@@ -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
@@ -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]