orgo 0.0.38__py3-none-any.whl → 0.0.39__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.
orgo/prompt.py
CHANGED
|
@@ -122,6 +122,13 @@ class Console:
|
|
|
122
122
|
timestamp = self._c(Colors.DIM, datetime.now().strftime("%H:%M:%S"))
|
|
123
123
|
print(f" {timestamp} {self._c(Colors.RED, '✗')} {self._c(Colors.RED, message)}")
|
|
124
124
|
|
|
125
|
+
def retry(self, attempt: int, max_attempts: int, delay: float):
|
|
126
|
+
"""Print retry message."""
|
|
127
|
+
if not self.verbose:
|
|
128
|
+
return
|
|
129
|
+
timestamp = self._c(Colors.DIM, datetime.now().strftime("%H:%M:%S"))
|
|
130
|
+
print(f" {timestamp} {self._c(Colors.YELLOW, '↻')} Retry {attempt}/{max_attempts} in {delay:.1f}s")
|
|
131
|
+
|
|
125
132
|
def success(self, iterations: int = 0):
|
|
126
133
|
"""Print success message."""
|
|
127
134
|
if not self.verbose:
|
|
@@ -138,6 +145,20 @@ class Console:
|
|
|
138
145
|
print()
|
|
139
146
|
|
|
140
147
|
|
|
148
|
+
# =============================================================================
|
|
149
|
+
# Exceptions
|
|
150
|
+
# =============================================================================
|
|
151
|
+
|
|
152
|
+
class ScreenshotError(Exception):
|
|
153
|
+
"""Raised when screenshot capture fails."""
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TransientVisionError(Exception):
|
|
158
|
+
"""Raised when Claude's vision API temporarily fails."""
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
|
|
141
162
|
# =============================================================================
|
|
142
163
|
# System Prompt
|
|
143
164
|
# =============================================================================
|
|
@@ -535,6 +556,8 @@ class AnthropicProvider:
|
|
|
535
556
|
thinking_enabled = kwargs.get("thinking_enabled", True)
|
|
536
557
|
thinking_budget = kwargs.get("thinking_budget", 1024)
|
|
537
558
|
max_saved_screenshots = kwargs.get("max_saved_screenshots", 3)
|
|
559
|
+
screenshot_retry_attempts = kwargs.get("screenshot_retry_attempts", 3)
|
|
560
|
+
screenshot_retry_delay = kwargs.get("screenshot_retry_delay", 2.0)
|
|
538
561
|
|
|
539
562
|
# System prompt
|
|
540
563
|
full_system_prompt = get_system_prompt(display_width, display_height, system_prompt)
|
|
@@ -581,15 +604,15 @@ class AnthropicProvider:
|
|
|
581
604
|
"budget_tokens": thinking_budget
|
|
582
605
|
}
|
|
583
606
|
|
|
584
|
-
# Call Claude
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
607
|
+
# Call Claude with retry logic
|
|
608
|
+
response = self._call_claude_with_retry(
|
|
609
|
+
client=client,
|
|
610
|
+
request_params=request_params,
|
|
611
|
+
messages=messages,
|
|
612
|
+
console=console,
|
|
613
|
+
max_retries=screenshot_retry_attempts,
|
|
614
|
+
retry_delay=screenshot_retry_delay
|
|
615
|
+
)
|
|
593
616
|
|
|
594
617
|
response_content = response.content
|
|
595
618
|
messages.append({"role": "assistant", "content": response_content})
|
|
@@ -627,11 +650,20 @@ class AnthropicProvider:
|
|
|
627
650
|
if callback:
|
|
628
651
|
callback("tool_use", {"action": action, "params": block.input})
|
|
629
652
|
|
|
630
|
-
# Execute tools
|
|
653
|
+
# Execute tools with retry logic
|
|
631
654
|
tool_results = []
|
|
632
655
|
for block in response_content:
|
|
633
656
|
if block.type == "tool_use":
|
|
634
|
-
result = self.
|
|
657
|
+
result = self._execute_tool_with_retry(
|
|
658
|
+
computer_id=computer_id,
|
|
659
|
+
params=block.input,
|
|
660
|
+
orgo_key=orgo_key,
|
|
661
|
+
orgo_url=orgo_url,
|
|
662
|
+
console=console,
|
|
663
|
+
callback=callback,
|
|
664
|
+
max_retries=screenshot_retry_attempts,
|
|
665
|
+
retry_delay=screenshot_retry_delay
|
|
666
|
+
)
|
|
635
667
|
|
|
636
668
|
tool_result = {"type": "tool_result", "tool_use_id": block.id}
|
|
637
669
|
|
|
@@ -653,6 +685,128 @@ class AnthropicProvider:
|
|
|
653
685
|
console.success(iteration)
|
|
654
686
|
return messages
|
|
655
687
|
|
|
688
|
+
def _call_claude_with_retry(
|
|
689
|
+
self,
|
|
690
|
+
client: anthropic.Anthropic,
|
|
691
|
+
request_params: Dict[str, Any],
|
|
692
|
+
messages: List[Dict[str, Any]],
|
|
693
|
+
console: Console,
|
|
694
|
+
max_retries: int = 3,
|
|
695
|
+
retry_delay: float = 2.0
|
|
696
|
+
) -> Any:
|
|
697
|
+
"""Call Claude API with exponential backoff retry logic."""
|
|
698
|
+
|
|
699
|
+
last_error = None
|
|
700
|
+
|
|
701
|
+
for attempt in range(max_retries):
|
|
702
|
+
try:
|
|
703
|
+
return client.beta.messages.create(**request_params)
|
|
704
|
+
|
|
705
|
+
except anthropic.BadRequestError as e:
|
|
706
|
+
error_msg = str(e).lower()
|
|
707
|
+
|
|
708
|
+
# Check for vision/image processing errors
|
|
709
|
+
if "image" in error_msg or "vision" in error_msg or "could not process" in error_msg:
|
|
710
|
+
last_error = TransientVisionError(f"Vision API error: {e}")
|
|
711
|
+
|
|
712
|
+
if attempt < max_retries - 1:
|
|
713
|
+
delay = retry_delay * (2 ** attempt) # Exponential backoff: 2s, 4s, 8s
|
|
714
|
+
console.retry(attempt + 1, max_retries, delay)
|
|
715
|
+
time.sleep(delay)
|
|
716
|
+
|
|
717
|
+
# Prune screenshots to reduce payload size
|
|
718
|
+
self._prune_screenshots(messages, 1)
|
|
719
|
+
request_params["messages"] = messages
|
|
720
|
+
continue
|
|
721
|
+
else:
|
|
722
|
+
raise last_error
|
|
723
|
+
|
|
724
|
+
# Check for base64 errors (fallback from old code)
|
|
725
|
+
elif "base64" in error_msg:
|
|
726
|
+
if attempt < max_retries - 1:
|
|
727
|
+
delay = retry_delay * (2 ** attempt)
|
|
728
|
+
console.retry(attempt + 1, max_retries, delay)
|
|
729
|
+
time.sleep(delay)
|
|
730
|
+
|
|
731
|
+
self._prune_screenshots(messages, 1)
|
|
732
|
+
request_params["messages"] = messages
|
|
733
|
+
continue
|
|
734
|
+
else:
|
|
735
|
+
raise
|
|
736
|
+
else:
|
|
737
|
+
# Non-retryable error
|
|
738
|
+
raise
|
|
739
|
+
|
|
740
|
+
except (anthropic.APIConnectionError, anthropic.APITimeoutError) as e:
|
|
741
|
+
# Network errors - retry with backoff
|
|
742
|
+
last_error = e
|
|
743
|
+
|
|
744
|
+
if attempt < max_retries - 1:
|
|
745
|
+
delay = retry_delay * (2 ** attempt)
|
|
746
|
+
console.retry(attempt + 1, max_retries, delay)
|
|
747
|
+
time.sleep(delay)
|
|
748
|
+
continue
|
|
749
|
+
else:
|
|
750
|
+
raise
|
|
751
|
+
|
|
752
|
+
except Exception as e:
|
|
753
|
+
# Unexpected errors - don't retry
|
|
754
|
+
raise
|
|
755
|
+
|
|
756
|
+
# Should never reach here, but just in case
|
|
757
|
+
if last_error:
|
|
758
|
+
raise last_error
|
|
759
|
+
raise RuntimeError("Max retries exceeded")
|
|
760
|
+
|
|
761
|
+
def _execute_tool_with_retry(
|
|
762
|
+
self,
|
|
763
|
+
computer_id: str,
|
|
764
|
+
params: Dict,
|
|
765
|
+
orgo_key: str,
|
|
766
|
+
orgo_url: str,
|
|
767
|
+
console: Console,
|
|
768
|
+
callback: Optional[Callable],
|
|
769
|
+
max_retries: int = 3,
|
|
770
|
+
retry_delay: float = 2.0
|
|
771
|
+
) -> Any:
|
|
772
|
+
"""Execute tool with retry logic for screenshots."""
|
|
773
|
+
|
|
774
|
+
action = params.get("action")
|
|
775
|
+
|
|
776
|
+
# Only retry screenshots, execute other actions directly
|
|
777
|
+
if action != "screenshot":
|
|
778
|
+
return self._execute_tool(computer_id, params, orgo_key, orgo_url, callback)
|
|
779
|
+
|
|
780
|
+
last_error = None
|
|
781
|
+
|
|
782
|
+
for attempt in range(max_retries):
|
|
783
|
+
try:
|
|
784
|
+
return self._execute_tool(computer_id, params, orgo_key, orgo_url, callback)
|
|
785
|
+
|
|
786
|
+
except (ScreenshotError, requests.exceptions.RequestException) as e:
|
|
787
|
+
last_error = e
|
|
788
|
+
|
|
789
|
+
if attempt < max_retries - 1:
|
|
790
|
+
delay = retry_delay * (2 ** attempt) # Exponential backoff
|
|
791
|
+
console.retry(attempt + 1, max_retries, delay)
|
|
792
|
+
time.sleep(delay)
|
|
793
|
+
continue
|
|
794
|
+
else:
|
|
795
|
+
# Return placeholder after all retries exhausted
|
|
796
|
+
logger.error(f"Screenshot failed after {max_retries} attempts: {e}")
|
|
797
|
+
return "Screenshot captured (degraded quality)"
|
|
798
|
+
|
|
799
|
+
except Exception as e:
|
|
800
|
+
# Unexpected errors - don't retry
|
|
801
|
+
raise
|
|
802
|
+
|
|
803
|
+
# Fallback if all retries failed
|
|
804
|
+
if last_error:
|
|
805
|
+
logger.error(f"Screenshot failed: {last_error}")
|
|
806
|
+
return "Screenshot captured (degraded quality)"
|
|
807
|
+
|
|
808
|
+
return "Screenshot captured"
|
|
809
|
+
|
|
656
810
|
def _execute_tool(self, computer_id: str, params: Dict, orgo_key: str, orgo_url: str, callback: Optional[Callable]) -> Any:
|
|
657
811
|
"""Execute a tool action via Orgo API."""
|
|
658
812
|
|
|
@@ -662,10 +816,10 @@ class AnthropicProvider:
|
|
|
662
816
|
|
|
663
817
|
try:
|
|
664
818
|
# =================================================================
|
|
665
|
-
# SCREENSHOT - GET request
|
|
819
|
+
# SCREENSHOT - GET request with validation
|
|
666
820
|
# =================================================================
|
|
667
821
|
if action == "screenshot":
|
|
668
|
-
r = requests.get(f"{base_url}/screenshot", headers=headers)
|
|
822
|
+
r = requests.get(f"{base_url}/screenshot", headers=headers, timeout=30)
|
|
669
823
|
r.raise_for_status()
|
|
670
824
|
|
|
671
825
|
data = r.json()
|
|
@@ -673,14 +827,21 @@ class AnthropicProvider:
|
|
|
673
827
|
|
|
674
828
|
if not image_url:
|
|
675
829
|
logger.error(f"Screenshot API returned no image URL: {data}")
|
|
676
|
-
|
|
830
|
+
raise ScreenshotError("No image URL in response")
|
|
677
831
|
|
|
678
|
-
|
|
832
|
+
# Fetch the actual image
|
|
833
|
+
img_r = requests.get(image_url, timeout=30)
|
|
679
834
|
img_r.raise_for_status()
|
|
680
835
|
|
|
836
|
+
# Validate image size
|
|
681
837
|
if len(img_r.content) < 100:
|
|
682
838
|
logger.error(f"Screenshot image too small: {len(img_r.content)} bytes")
|
|
683
|
-
|
|
839
|
+
raise ScreenshotError(f"Invalid image size: {len(img_r.content)} bytes")
|
|
840
|
+
|
|
841
|
+
# Validate it's actually an image
|
|
842
|
+
if not img_r.headers.get('content-type', '').startswith('image/'):
|
|
843
|
+
logger.error(f"Invalid content type: {img_r.headers.get('content-type')}")
|
|
844
|
+
raise ScreenshotError("Response is not an image")
|
|
684
845
|
|
|
685
846
|
image_b64 = base64.b64encode(img_r.content).decode()
|
|
686
847
|
|
|
@@ -793,10 +954,16 @@ class AnthropicProvider:
|
|
|
793
954
|
return f"Unknown action: {action}"
|
|
794
955
|
|
|
795
956
|
except requests.exceptions.RequestException as e:
|
|
796
|
-
|
|
797
|
-
|
|
957
|
+
if action == "screenshot":
|
|
958
|
+
# Re-raise as ScreenshotError for retry logic
|
|
959
|
+
raise ScreenshotError(f"Screenshot request failed: {e}") from e
|
|
960
|
+
else:
|
|
961
|
+
logger.error(f"API request failed for {action}: {e}")
|
|
962
|
+
return f"Action {action} completed"
|
|
798
963
|
except Exception as e:
|
|
799
964
|
logger.error(f"Error executing {action}: {e}")
|
|
965
|
+
if action == "screenshot":
|
|
966
|
+
raise ScreenshotError(f"Screenshot processing failed: {e}") from e
|
|
800
967
|
return f"Action {action} completed"
|
|
801
968
|
|
|
802
969
|
def _prune_screenshots(self, messages: List[Dict], keep: int):
|
|
@@ -815,6 +982,7 @@ class AnthropicProvider:
|
|
|
815
982
|
if isinstance(item, dict) and item.get("type") == "image":
|
|
816
983
|
images.append(item)
|
|
817
984
|
|
|
985
|
+
# Replace older screenshots with 1x1 transparent PNG
|
|
818
986
|
for img in images[:-keep]:
|
|
819
987
|
if "source" in img:
|
|
820
988
|
img["source"]["data"] = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
|
|
@@ -2,12 +2,12 @@ orgo/__init__.py,sha256=7uMYbhVNVUtrH7mCd8Ofll36EIdR92LcVlF7bdKuVR4,206
|
|
|
2
2
|
orgo/computer.py,sha256=7yNxlIk7j6yIcYXDF8HQfJU1uHNHvO9lLP4TTVZEuy4,18661
|
|
3
3
|
orgo/forge.py,sha256=Ey_X3tZwlgHPCSBYBIxPInNaERARoDZQ3vxjvOBLn_I,6714
|
|
4
4
|
orgo/project.py,sha256=Abn58FKAL3vety-MzHJDR-G0GFnEDSD_ljTYnXp9e1I,3148
|
|
5
|
-
orgo/prompt.py,sha256=
|
|
5
|
+
orgo/prompt.py,sha256=EluyrgMLfgQ8C6kR4l_jKhu6mnCuSL14PGYO1AE4FKk,39685
|
|
6
6
|
orgo/api/__init__.py,sha256=9Tzb_OPJ5DH7Cg7OrHzpZZUT4ip05alpa9RLDYmnId8,113
|
|
7
7
|
orgo/api/client.py,sha256=VGdlBCu2gAdDwMZ55n7kQS4R-CFXJjLByXPmRlMLoiY,9097
|
|
8
8
|
orgo/utils/__init__.py,sha256=W4G_nwGBf_7jy0w_mfcrkllurYHSRU4B5cMTVYH_uCc,123
|
|
9
9
|
orgo/utils/auth.py,sha256=tPLBJY-6gdBQWLUjUbwIwxHphC3KoRT_XgP3Iykw3Mw,509
|
|
10
|
-
orgo-0.0.
|
|
11
|
-
orgo-0.0.
|
|
12
|
-
orgo-0.0.
|
|
13
|
-
orgo-0.0.
|
|
10
|
+
orgo-0.0.39.dist-info/METADATA,sha256=VIc2ar6DhP0mQ9RVYrGRlTNPuKO3DUu9kwjJVC_E3cY,894
|
|
11
|
+
orgo-0.0.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
orgo-0.0.39.dist-info/top_level.txt,sha256=q0rYtFji8GbYuhFW8A5Ab9e0j27761IKPhnL0E9xow4,5
|
|
13
|
+
orgo-0.0.39.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|