patchpal 0.4.1__tar.gz → 0.4.3__tar.gz
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.
- {patchpal-0.4.1/patchpal.egg-info → patchpal-0.4.3}/PKG-INFO +1 -1
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal/__init__.py +1 -1
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal/agent.py +43 -4
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal/tools.py +10 -6
- {patchpal-0.4.1 → patchpal-0.4.3/patchpal.egg-info}/PKG-INFO +1 -1
- {patchpal-0.4.1 → patchpal-0.4.3}/tests/test_agent.py +27 -14
- {patchpal-0.4.1 → patchpal-0.4.3}/LICENSE +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/MANIFEST.in +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/README.md +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal/cli.py +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal/context.py +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal/permissions.py +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal/skills.py +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal/system_prompt.md +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal.egg-info/SOURCES.txt +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal.egg-info/dependency_links.txt +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal.egg-info/entry_points.txt +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal.egg-info/requires.txt +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/patchpal.egg-info/top_level.txt +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/pyproject.toml +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/setup.cfg +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/tests/test_cli.py +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/tests/test_context.py +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/tests/test_guardrails.py +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/tests/test_operational_safety.py +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/tests/test_skills.py +0 -0
- {patchpal-0.4.1 → patchpal-0.4.3}/tests/test_tools.py +0 -0
|
@@ -756,13 +756,52 @@ def _apply_prompt_caching(messages: List[Dict[str, Any]], model_id: str) -> List
|
|
|
756
756
|
|
|
757
757
|
# Apply caching to system messages (first 2)
|
|
758
758
|
for idx in system_messages[:2]:
|
|
759
|
-
|
|
760
|
-
|
|
759
|
+
msg = messages[idx]
|
|
760
|
+
# Skip if already has cache marker at content block level
|
|
761
|
+
if isinstance(msg.get("content"), list):
|
|
762
|
+
# Already structured - check if any block has cache_control/cachePoint
|
|
763
|
+
has_cache = any(
|
|
764
|
+
"cache_control" in block or "cachePoint" in block
|
|
765
|
+
for block in msg["content"]
|
|
766
|
+
if isinstance(block, dict)
|
|
767
|
+
)
|
|
768
|
+
if not has_cache and msg["content"]:
|
|
769
|
+
# Add cache marker to the last content block
|
|
770
|
+
last_block = msg["content"][-1]
|
|
771
|
+
if isinstance(last_block, dict):
|
|
772
|
+
last_block.update(cache_marker)
|
|
773
|
+
else:
|
|
774
|
+
# Convert simple string content to structured format with cache marker
|
|
775
|
+
content_text = msg.get("content", "")
|
|
776
|
+
messages[idx] = {
|
|
777
|
+
**msg,
|
|
778
|
+
"content": [{"type": "text", "text": content_text, **cache_marker}],
|
|
779
|
+
}
|
|
761
780
|
|
|
762
781
|
# Apply caching to last 2 messages
|
|
763
782
|
for idx in last_two_indices:
|
|
764
|
-
|
|
765
|
-
|
|
783
|
+
msg = messages[idx]
|
|
784
|
+
# Skip if already has cache marker at content block level
|
|
785
|
+
if isinstance(msg.get("content"), list):
|
|
786
|
+
# Already structured - check if any block has cache_control/cachePoint
|
|
787
|
+
has_cache = any(
|
|
788
|
+
"cache_control" in block or "cachePoint" in block
|
|
789
|
+
for block in msg["content"]
|
|
790
|
+
if isinstance(block, dict)
|
|
791
|
+
)
|
|
792
|
+
if not has_cache and msg["content"]:
|
|
793
|
+
# Add cache marker to the last content block
|
|
794
|
+
last_block = msg["content"][-1]
|
|
795
|
+
if isinstance(last_block, dict):
|
|
796
|
+
last_block.update(cache_marker)
|
|
797
|
+
else:
|
|
798
|
+
# Convert simple string content to structured format with cache marker
|
|
799
|
+
content_text = msg.get("content", "")
|
|
800
|
+
if content_text: # Only convert non-empty content
|
|
801
|
+
messages[idx] = {
|
|
802
|
+
**msg,
|
|
803
|
+
"content": [{"type": "text", "text": content_text, **cache_marker}],
|
|
804
|
+
}
|
|
766
805
|
|
|
767
806
|
return messages
|
|
768
807
|
|
|
@@ -665,7 +665,7 @@ def _check_path(path: str, must_exist: bool = True) -> Path:
|
|
|
665
665
|
Validate and resolve a path.
|
|
666
666
|
|
|
667
667
|
Args:
|
|
668
|
-
path: Path to validate (relative or
|
|
668
|
+
path: Path to validate (relative, absolute, or with ~ for home directory)
|
|
669
669
|
must_exist: Whether the file must exist
|
|
670
670
|
|
|
671
671
|
Returns:
|
|
@@ -678,12 +678,15 @@ def _check_path(path: str, must_exist: bool = True) -> Path:
|
|
|
678
678
|
Can access files anywhere on the system (repository or outside).
|
|
679
679
|
Sensitive files (.env, credentials) are always blocked for safety.
|
|
680
680
|
"""
|
|
681
|
+
# Expand ~ for home directory first
|
|
682
|
+
expanded_path = os.path.expanduser(path)
|
|
683
|
+
|
|
681
684
|
# Resolve path (handle both absolute and relative paths)
|
|
682
|
-
path_obj = Path(
|
|
685
|
+
path_obj = Path(expanded_path)
|
|
683
686
|
if path_obj.is_absolute():
|
|
684
687
|
p = path_obj.resolve()
|
|
685
688
|
else:
|
|
686
|
-
p = (REPO_ROOT /
|
|
689
|
+
p = (REPO_ROOT / expanded_path).resolve()
|
|
687
690
|
|
|
688
691
|
# Check if file exists when required
|
|
689
692
|
if must_exist and not p.is_file():
|
|
@@ -1074,12 +1077,13 @@ def tree(path: str = ".", max_depth: int = 3, show_hidden: bool = False) -> str:
|
|
|
1074
1077
|
# Limit max_depth
|
|
1075
1078
|
max_depth = min(max_depth, 10)
|
|
1076
1079
|
|
|
1077
|
-
#
|
|
1078
|
-
|
|
1080
|
+
# Expand ~ for home directory and resolve path (handle both absolute and relative paths)
|
|
1081
|
+
expanded_path = os.path.expanduser(path)
|
|
1082
|
+
path_obj = Path(expanded_path)
|
|
1079
1083
|
if path_obj.is_absolute():
|
|
1080
1084
|
start_path = path_obj.resolve()
|
|
1081
1085
|
else:
|
|
1082
|
-
start_path = (REPO_ROOT /
|
|
1086
|
+
start_path = (REPO_ROOT / expanded_path).resolve()
|
|
1083
1087
|
|
|
1084
1088
|
# Check if path exists and is a directory
|
|
1085
1089
|
if not start_path.exists():
|
|
@@ -441,13 +441,17 @@ def test_prompt_caching_application_anthropic():
|
|
|
441
441
|
# Test with direct Anthropic API
|
|
442
442
|
cached_messages = _apply_prompt_caching(messages.copy(), "anthropic/claude-sonnet-4-5")
|
|
443
443
|
|
|
444
|
-
# System message should have cache_control
|
|
445
|
-
assert
|
|
446
|
-
assert cached_messages[0]["
|
|
444
|
+
# System message should have cache_control inside content block
|
|
445
|
+
assert isinstance(cached_messages[0]["content"], list)
|
|
446
|
+
assert cached_messages[0]["content"][0]["type"] == "text"
|
|
447
|
+
assert "cache_control" in cached_messages[0]["content"][0]
|
|
448
|
+
assert cached_messages[0]["content"][0]["cache_control"] == {"type": "ephemeral"}
|
|
447
449
|
|
|
448
|
-
# Last 2 messages should have cache_control
|
|
449
|
-
assert
|
|
450
|
-
assert "cache_control" in cached_messages[-
|
|
450
|
+
# Last 2 messages should have cache_control inside content blocks
|
|
451
|
+
assert isinstance(cached_messages[-1]["content"], list) # Last user message
|
|
452
|
+
assert "cache_control" in cached_messages[-1]["content"][0]
|
|
453
|
+
assert isinstance(cached_messages[-2]["content"], list) # Last assistant message
|
|
454
|
+
assert "cache_control" in cached_messages[-2]["content"][0]
|
|
451
455
|
|
|
452
456
|
|
|
453
457
|
def test_prompt_caching_application_bedrock():
|
|
@@ -466,13 +470,17 @@ def test_prompt_caching_application_bedrock():
|
|
|
466
470
|
messages.copy(), "bedrock/anthropic.claude-sonnet-4-5-v1:0"
|
|
467
471
|
)
|
|
468
472
|
|
|
469
|
-
# System message should have cachePoint (Bedrock format)
|
|
470
|
-
assert
|
|
471
|
-
assert cached_messages[0]["
|
|
473
|
+
# System message should have cachePoint inside content block (Bedrock format)
|
|
474
|
+
assert isinstance(cached_messages[0]["content"], list)
|
|
475
|
+
assert cached_messages[0]["content"][0]["type"] == "text"
|
|
476
|
+
assert "cachePoint" in cached_messages[0]["content"][0]
|
|
477
|
+
assert cached_messages[0]["content"][0]["cachePoint"] == {"type": "ephemeral"}
|
|
472
478
|
|
|
473
|
-
# Last 2 messages should have cachePoint
|
|
474
|
-
assert
|
|
475
|
-
assert "cachePoint" in cached_messages[-
|
|
479
|
+
# Last 2 messages should have cachePoint inside content blocks
|
|
480
|
+
assert isinstance(cached_messages[-1]["content"], list)
|
|
481
|
+
assert "cachePoint" in cached_messages[-1]["content"][0]
|
|
482
|
+
assert isinstance(cached_messages[-2]["content"], list)
|
|
483
|
+
assert "cachePoint" in cached_messages[-2]["content"][0]
|
|
476
484
|
|
|
477
485
|
|
|
478
486
|
def test_prompt_caching_no_modification_for_unsupported():
|
|
@@ -506,5 +514,10 @@ def test_prompt_caching_idempotent():
|
|
|
506
514
|
cached_once = _apply_prompt_caching(messages.copy(), "anthropic/claude-sonnet-4-5")
|
|
507
515
|
cached_twice = _apply_prompt_caching(cached_once.copy(), "anthropic/claude-sonnet-4-5")
|
|
508
516
|
|
|
509
|
-
# Should
|
|
510
|
-
assert cached_once == cached_twice
|
|
517
|
+
# Should have the same structure after second application
|
|
518
|
+
assert cached_once[0]["content"][0] == cached_twice[0]["content"][0]
|
|
519
|
+
assert cached_once[1]["content"][0] == cached_twice[1]["content"][0]
|
|
520
|
+
|
|
521
|
+
# Should only have one cache_control marker per message
|
|
522
|
+
assert len([k for k in cached_twice[0]["content"][0].keys() if "cache" in k]) == 1
|
|
523
|
+
assert len([k for k in cached_twice[1]["content"][0].keys() if "cache" in k]) == 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|