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
- try:
586
- response = client.beta.messages.create(**request_params)
587
- except Exception as e:
588
- if "base64" in str(e).lower():
589
- self._prune_screenshots(messages, 1)
590
- response = client.beta.messages.create(**request_params)
591
- else:
592
- raise
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._execute_tool(computer_id, block.input, orgo_key, orgo_url, callback)
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
- return "Screenshot captured"
830
+ raise ScreenshotError("No image URL in response")
677
831
 
678
- img_r = requests.get(image_url)
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
- return "Screenshot captured"
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
- logger.error(f"API request failed for {action}: {e}")
797
- return f"Action {action} completed"
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=="
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orgo
3
- Version: 0.0.38
3
+ Version: 0.0.39
4
4
  Summary: Computers for AI agents
5
5
  Author: Orgo Team
6
6
  License: MIT
@@ -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=WQEtNwEAP8VSD9bum-Zx3pGvR_sH4OWm2nhmQx4of1Y,32820
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.38.dist-info/METADATA,sha256=MZobQck4x8Oe-BNauzyRjY_GOjLXxrc6ee1dWXGIcpQ,894
11
- orgo-0.0.38.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- orgo-0.0.38.dist-info/top_level.txt,sha256=q0rYtFji8GbYuhFW8A5Ab9e0j27761IKPhnL0E9xow4,5
13
- orgo-0.0.38.dist-info/RECORD,,
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