deepagents 0.3.8__py3-none-any.whl → 0.3.10__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.
- deepagents/__init__.py +3 -1
- deepagents/_version.py +3 -0
- deepagents/backends/__init__.py +2 -0
- deepagents/backends/composite.py +2 -2
- deepagents/backends/filesystem.py +13 -21
- deepagents/backends/local_shell.py +305 -0
- deepagents/backends/sandbox.py +431 -24
- deepagents/backends/utils.py +69 -24
- deepagents/middleware/filesystem.py +482 -522
- deepagents/middleware/skills.py +1 -1
- deepagents/middleware/subagents.py +23 -9
- deepagents/middleware/summarization.py +9 -4
- deepagents/py.typed +0 -0
- deepagents-0.3.10.dist-info/METADATA +76 -0
- deepagents-0.3.10.dist-info/RECORD +25 -0
- {deepagents-0.3.8.dist-info → deepagents-0.3.10.dist-info}/WHEEL +1 -1
- deepagents-0.3.8.dist-info/METADATA +0 -527
- deepagents-0.3.8.dist-info/RECORD +0 -22
- {deepagents-0.3.8.dist-info → deepagents-0.3.10.dist-info}/top_level.txt +0 -0
deepagents/backends/utils.py
CHANGED
|
@@ -219,17 +219,26 @@ def truncate_if_too_long(result: list[str] | str) -> list[str] | str:
|
|
|
219
219
|
return result
|
|
220
220
|
|
|
221
221
|
|
|
222
|
-
def
|
|
223
|
-
"""
|
|
222
|
+
def _normalize_path(path: str | None) -> str:
|
|
223
|
+
"""Normalize a path to canonical form.
|
|
224
|
+
|
|
225
|
+
Converts path to absolute form starting with /, removes trailing slashes
|
|
226
|
+
(except for root), and validates that the path is not empty.
|
|
224
227
|
|
|
225
228
|
Args:
|
|
226
|
-
path: Path to
|
|
229
|
+
path: Path to normalize (None defaults to "/")
|
|
227
230
|
|
|
228
231
|
Returns:
|
|
229
|
-
Normalized path starting with /
|
|
232
|
+
Normalized path starting with / (without trailing slash unless it's root)
|
|
230
233
|
|
|
231
234
|
Raises:
|
|
232
|
-
ValueError: If path is invalid
|
|
235
|
+
ValueError: If path is invalid (empty string after strip)
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
_normalize_path(None) -> "/"
|
|
239
|
+
_normalize_path("/dir/") -> "/dir"
|
|
240
|
+
_normalize_path("dir") -> "/dir"
|
|
241
|
+
_normalize_path("/") -> "/"
|
|
233
242
|
"""
|
|
234
243
|
path = path or "/"
|
|
235
244
|
if not path or path.strip() == "":
|
|
@@ -237,12 +246,43 @@ def _validate_path(path: str | None) -> str:
|
|
|
237
246
|
|
|
238
247
|
normalized = path if path.startswith("/") else "/" + path
|
|
239
248
|
|
|
240
|
-
|
|
241
|
-
|
|
249
|
+
# Only root should have trailing slash
|
|
250
|
+
if normalized != "/" and normalized.endswith("/"):
|
|
251
|
+
normalized = normalized.rstrip("/")
|
|
242
252
|
|
|
243
253
|
return normalized
|
|
244
254
|
|
|
245
255
|
|
|
256
|
+
def _filter_files_by_path(files: dict[str, Any], normalized_path: str) -> dict[str, Any]:
|
|
257
|
+
"""Filter files dict by normalized path, handling exact file matches and directory prefixes.
|
|
258
|
+
|
|
259
|
+
Expects a normalized path from _normalize_path (no trailing slash except root).
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
files: Dictionary mapping file paths to file data
|
|
263
|
+
normalized_path: Normalized path from _normalize_path (e.g., "/", "/dir", "/dir/file")
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Filtered dictionary of files matching the path
|
|
267
|
+
|
|
268
|
+
Example:
|
|
269
|
+
files = {"/dir/file": {...}, "/dir/other": {...}}
|
|
270
|
+
_filter_files_by_path(files, "/dir/file") # Returns {"/dir/file": {...}}
|
|
271
|
+
_filter_files_by_path(files, "/dir") # Returns both files
|
|
272
|
+
"""
|
|
273
|
+
# Check if path matches an exact file
|
|
274
|
+
if normalized_path in files:
|
|
275
|
+
return {normalized_path: files[normalized_path]}
|
|
276
|
+
|
|
277
|
+
# Otherwise treat as directory prefix
|
|
278
|
+
if normalized_path == "/":
|
|
279
|
+
# Root directory - match all files starting with /
|
|
280
|
+
return {fp: fd for fp, fd in files.items() if fp.startswith("/")}
|
|
281
|
+
# Non-root directory - add trailing slash for prefix matching
|
|
282
|
+
dir_prefix = normalized_path + "/"
|
|
283
|
+
return {fp: fd for fp, fd in files.items() if fp.startswith(dir_prefix)}
|
|
284
|
+
|
|
285
|
+
|
|
246
286
|
def _glob_search_files(
|
|
247
287
|
files: dict[str, Any],
|
|
248
288
|
pattern: str,
|
|
@@ -267,11 +307,11 @@ def _glob_search_files(
|
|
|
267
307
|
```
|
|
268
308
|
"""
|
|
269
309
|
try:
|
|
270
|
-
normalized_path =
|
|
310
|
+
normalized_path = _normalize_path(path)
|
|
271
311
|
except ValueError:
|
|
272
312
|
return "No files found"
|
|
273
313
|
|
|
274
|
-
filtered =
|
|
314
|
+
filtered = _filter_files_by_path(files, normalized_path)
|
|
275
315
|
|
|
276
316
|
# Respect standard glob semantics:
|
|
277
317
|
# - Patterns without path separators (e.g., "*.py") match only in the current
|
|
@@ -281,9 +321,17 @@ def _glob_search_files(
|
|
|
281
321
|
|
|
282
322
|
matches = []
|
|
283
323
|
for file_path, file_data in filtered.items():
|
|
284
|
-
relative
|
|
285
|
-
|
|
324
|
+
# Compute relative path for glob matching
|
|
325
|
+
# If normalized_path is "/dir", we want "/dir/file.txt" -> "file.txt"
|
|
326
|
+
# If normalized_path is "/dir/file.txt" (exact file), we want "file.txt"
|
|
327
|
+
if normalized_path == "/":
|
|
328
|
+
relative = file_path[1:] # Remove leading slash
|
|
329
|
+
elif file_path == normalized_path:
|
|
330
|
+
# Exact file match - use just the filename
|
|
286
331
|
relative = file_path.split("/")[-1]
|
|
332
|
+
else:
|
|
333
|
+
# Directory prefix - strip the directory path
|
|
334
|
+
relative = file_path[len(normalized_path) + 1 :] # +1 for the slash
|
|
287
335
|
|
|
288
336
|
if wcglob.globmatch(relative, effective_pattern, flags=wcglob.BRACE | wcglob.GLOBSTAR):
|
|
289
337
|
matches.append((file_path, file_data["modified_at"]))
|
|
@@ -357,11 +405,11 @@ def _grep_search_files(
|
|
|
357
405
|
return f"Invalid regex pattern: {e}"
|
|
358
406
|
|
|
359
407
|
try:
|
|
360
|
-
normalized_path =
|
|
408
|
+
normalized_path = _normalize_path(path)
|
|
361
409
|
except ValueError:
|
|
362
410
|
return "No matches found"
|
|
363
411
|
|
|
364
|
-
filtered =
|
|
412
|
+
filtered = _filter_files_by_path(files, normalized_path)
|
|
365
413
|
|
|
366
414
|
if glob:
|
|
367
415
|
filtered = {fp: fd for fp, fd in filtered.items() if wcglob.globmatch(Path(fp).name, glob, flags=wcglob.BRACE)}
|
|
@@ -390,21 +438,18 @@ def grep_matches_from_files(
|
|
|
390
438
|
) -> list[GrepMatch] | str:
|
|
391
439
|
"""Return structured grep matches from an in-memory files mapping.
|
|
392
440
|
|
|
393
|
-
|
|
394
|
-
(e.g., invalid regex). We deliberately do not raise here to keep backends
|
|
395
|
-
non-throwing in tool contexts and preserve user-facing error messages.
|
|
396
|
-
"""
|
|
397
|
-
try:
|
|
398
|
-
regex = re.compile(pattern)
|
|
399
|
-
except re.error as e:
|
|
400
|
-
return f"Invalid regex pattern: {e}"
|
|
441
|
+
Performs literal text search (not regex).
|
|
401
442
|
|
|
443
|
+
Returns a list of GrepMatch on success, or a string for invalid inputs.
|
|
444
|
+
We deliberately do not raise here to keep backends non-throwing in tool
|
|
445
|
+
contexts and preserve user-facing error messages.
|
|
446
|
+
"""
|
|
402
447
|
try:
|
|
403
|
-
normalized_path =
|
|
448
|
+
normalized_path = _normalize_path(path)
|
|
404
449
|
except ValueError:
|
|
405
450
|
return []
|
|
406
451
|
|
|
407
|
-
filtered =
|
|
452
|
+
filtered = _filter_files_by_path(files, normalized_path)
|
|
408
453
|
|
|
409
454
|
if glob:
|
|
410
455
|
filtered = {fp: fd for fp, fd in filtered.items() if wcglob.globmatch(Path(fp).name, glob, flags=wcglob.BRACE)}
|
|
@@ -412,7 +457,7 @@ def grep_matches_from_files(
|
|
|
412
457
|
matches: list[GrepMatch] = []
|
|
413
458
|
for file_path, file_data in filtered.items():
|
|
414
459
|
for line_num, line in enumerate(file_data["content"], 1):
|
|
415
|
-
if
|
|
460
|
+
if pattern in line: # Simple substring search for literal matching
|
|
416
461
|
matches.append({"path": file_path, "line": int(line_num), "text": line})
|
|
417
462
|
return matches
|
|
418
463
|
|