black-yt commited on
Commit
6466303
·
1 Parent(s): 9336907

Sync Bash output handling

Browse files
VERSION CHANGED
@@ -1 +1 @@
1
- v0.0.47
 
1
+ v0.0.48
agent_base/react_agent.py CHANGED
@@ -1324,13 +1324,17 @@ class MultiTurnReactAgent(BaseAgent):
1324
  )
1325
 
1326
  def execute_tool_item(item: dict[str, Any]) -> tuple[dict[str, Any], Any]:
1327
- result = self.custom_call_tool(
1328
- str(item["tool_name"]),
1329
- item["tool_arguments"],
1330
- workspace_root=resolved_workspace_root,
1331
- runtime_deadline=runtime_deadline,
1332
- model_name=self.model,
1333
- )
 
 
 
 
1334
  return item, result
1335
 
1336
  for batch_indexes in tool_execution_batches([str(item["tool_name"]) for item in tool_call_items]):
 
1324
  )
1325
 
1326
  def execute_tool_item(item: dict[str, Any]) -> tuple[dict[str, Any], Any]:
1327
+ tool_name = str(item["tool_name"])
1328
+ try:
1329
+ result = self.custom_call_tool(
1330
+ tool_name,
1331
+ item["tool_arguments"],
1332
+ workspace_root=resolved_workspace_root,
1333
+ runtime_deadline=runtime_deadline,
1334
+ model_name=self.model,
1335
+ )
1336
+ except Exception as exc:
1337
+ result = f"[{tool_name}] Tool execution error: {type(exc).__name__}: {exc}"
1338
  return item, result
1339
 
1340
  for batch_indexes in tool_execution_batches([str(item["tool_name"]) for item in tool_call_items]):
agent_base/tools/tool_runtime.py CHANGED
@@ -31,6 +31,7 @@ DEFAULT_OUTPUT_CHARS = 20000
31
  DEFAULT_YIELD_MS = 200
32
  REPEAT_COLLAPSE_THRESHOLD = 3
33
 
 
34
  def _default_shell() -> str:
35
  return shutil.which("bash") or "/bin/bash"
36
 
@@ -100,6 +101,54 @@ def _bounded_output(text: str, *, max_output_chars: int = DEFAULT_OUTPUT_CHARS)
100
  return compressed[:keep] + suffix
101
 
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  class Bash(ToolBase):
104
  name = "Bash"
105
  description = (
@@ -178,7 +227,7 @@ class Bash(ToolBase):
178
  command,
179
  shell=True,
180
  capture_output=True,
181
- text=True,
182
  timeout=effective_timeout,
183
  cwd=str(cwd),
184
  env=sanitized_subprocess_env(base_root=base_root),
@@ -190,8 +239,8 @@ class Bash(ToolBase):
190
  return f"[Bash] Error executing command: {exc}"
191
 
192
  parts = [f"exit_code: {proc.returncode}"]
193
- stdout = _bounded_output(proc.stdout, max_output_chars=max_output_chars)
194
- stderr = _bounded_output(proc.stderr, max_output_chars=max_output_chars)
195
  if stdout:
196
  parts.append(f"stdout:\n{stdout}")
197
  if stderr:
 
31
  DEFAULT_YIELD_MS = 200
32
  REPEAT_COLLAPSE_THRESHOLD = 3
33
 
34
+
35
  def _default_shell() -> str:
36
  return shutil.which("bash") or "/bin/bash"
37
 
 
101
  return compressed[:keep] + suffix
102
 
103
 
104
+ def _looks_binary_output(data: bytes) -> bool:
105
+ if not data:
106
+ return False
107
+ sample = data[:4096]
108
+ if b"\x00" in sample:
109
+ return True
110
+ allowed_controls = {9, 10, 12, 13}
111
+ control_count = sum(1 for byte in sample if byte < 32 and byte not in allowed_controls)
112
+ return control_count / max(len(sample), 1) > 0.05
113
+
114
+
115
+ def _truncate_output_bytes(data: bytes, *, max_output_chars: int) -> tuple[bytes, bool, int]:
116
+ max_output_bytes = max(1, int(max_output_chars))
117
+ if len(data) <= max_output_bytes:
118
+ return data, False, 0
119
+ omitted = len(data) - max_output_bytes
120
+ suffix = f"\n[output truncated before decoding: omitted {omitted} bytes]\n".encode("utf-8")
121
+ keep = max(0, max_output_bytes - len(suffix))
122
+ return data[:keep] + suffix, True, omitted
123
+
124
+
125
+ def _safe_process_output(data: Union[str, bytes, None], *, max_output_chars: int = DEFAULT_OUTPUT_CHARS) -> str:
126
+ if data is None or data == "":
127
+ return ""
128
+ if isinstance(data, str):
129
+ return _bounded_output(data, max_output_chars=max_output_chars)
130
+
131
+ if not data:
132
+ return ""
133
+ if _looks_binary_output(data):
134
+ note = (
135
+ f"[binary output omitted: {len(data)} bytes. "
136
+ "Use file or a format-aware reader such as openpyxl/scipy.io.loadmat for binary files.]"
137
+ )
138
+ if len(data) > max_output_chars:
139
+ note += f"\n[output truncated before decoding: omitted {len(data) - max_output_chars} bytes]"
140
+ return note
141
+
142
+ bounded, _, _ = _truncate_output_bytes(data, max_output_chars=max_output_chars)
143
+ try:
144
+ decoded = bounded.decode("utf-8")
145
+ prefix = ""
146
+ except UnicodeDecodeError:
147
+ decoded = bounded.decode("utf-8", errors="replace")
148
+ prefix = "[non-UTF-8 bytes decoded with replacement characters]\n"
149
+ return _bounded_output(prefix + decoded, max_output_chars=max_output_chars)
150
+
151
+
152
  class Bash(ToolBase):
153
  name = "Bash"
154
  description = (
 
227
  command,
228
  shell=True,
229
  capture_output=True,
230
+ text=False,
231
  timeout=effective_timeout,
232
  cwd=str(cwd),
233
  env=sanitized_subprocess_env(base_root=base_root),
 
239
  return f"[Bash] Error executing command: {exc}"
240
 
241
  parts = [f"exit_code: {proc.returncode}"]
242
+ stdout = _safe_process_output(proc.stdout, max_output_chars=max_output_chars)
243
+ stderr = _safe_process_output(proc.stderr, max_output_chars=max_output_chars)
244
  if stdout:
245
  parts.append(f"stdout:\n{stdout}")
246
  if stderr: