cua-agent 0.1.41__tar.gz → 0.1.43__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.

Potentially problematic release.


This version of cua-agent might be problematic. Click here for more details.

Files changed (85) hide show
  1. {cua_agent-0.1.41 → cua_agent-0.1.43}/PKG-INFO +1 -1
  2. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/base.py +9 -0
  3. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/loop.py +28 -2
  4. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/loop.py +82 -9
  5. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/loop.py +31 -5
  6. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/loop.py +150 -75
  7. {cua_agent-0.1.41 → cua_agent-0.1.43}/pyproject.toml +3 -3
  8. {cua_agent-0.1.41 → cua_agent-0.1.43}/README.md +0 -0
  9. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/__init__.py +0 -0
  10. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/__init__.py +0 -0
  11. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/agent.py +0 -0
  12. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/callbacks.py +0 -0
  13. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/experiment.py +0 -0
  14. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/factory.py +0 -0
  15. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/messages.py +0 -0
  16. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/provider_config.py +0 -0
  17. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/telemetry.py +0 -0
  18. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/tools/__init__.py +0 -0
  19. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/tools/base.py +0 -0
  20. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/tools/bash.py +0 -0
  21. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/tools/collection.py +0 -0
  22. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/tools/computer.py +0 -0
  23. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/tools/edit.py +0 -0
  24. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/tools/manager.py +0 -0
  25. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/tools.py +0 -0
  26. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/types.py +0 -0
  27. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/core/visualization.py +0 -0
  28. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/__init__.py +0 -0
  29. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/__init__.py +0 -0
  30. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/api/client.py +0 -0
  31. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/api/logging.py +0 -0
  32. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/api_handler.py +0 -0
  33. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/callbacks/__init__.py +0 -0
  34. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/callbacks/manager.py +0 -0
  35. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/prompts.py +0 -0
  36. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/response_handler.py +0 -0
  37. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/tools/__init__.py +0 -0
  38. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/tools/base.py +0 -0
  39. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/tools/bash.py +0 -0
  40. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/tools/collection.py +0 -0
  41. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/tools/computer.py +0 -0
  42. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/tools/edit.py +0 -0
  43. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/tools/manager.py +0 -0
  44. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/tools/run.py +0 -0
  45. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/types.py +0 -0
  46. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/anthropic/utils.py +0 -0
  47. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/__init__.py +0 -0
  48. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/api_handler.py +0 -0
  49. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/clients/anthropic.py +0 -0
  50. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/clients/base.py +0 -0
  51. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/clients/oaicompat.py +0 -0
  52. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/clients/ollama.py +0 -0
  53. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/clients/openai.py +0 -0
  54. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/clients/utils.py +0 -0
  55. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/image_utils.py +0 -0
  56. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/parser.py +0 -0
  57. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/prompts.py +0 -0
  58. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/tools/__init__.py +0 -0
  59. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/tools/base.py +0 -0
  60. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/tools/bash.py +0 -0
  61. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/tools/computer.py +0 -0
  62. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/tools/manager.py +0 -0
  63. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/omni/utils.py +0 -0
  64. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/__init__.py +0 -0
  65. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/api_handler.py +0 -0
  66. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/response_handler.py +0 -0
  67. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/tools/__init__.py +0 -0
  68. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/tools/base.py +0 -0
  69. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/tools/computer.py +0 -0
  70. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/tools/manager.py +0 -0
  71. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/types.py +0 -0
  72. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/openai/utils.py +0 -0
  73. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/__init__.py +0 -0
  74. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/clients/base.py +0 -0
  75. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/clients/mlxvlm.py +0 -0
  76. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/clients/oaicompat.py +0 -0
  77. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/prompts.py +0 -0
  78. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/tools/__init__.py +0 -0
  79. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/tools/computer.py +0 -0
  80. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/tools/manager.py +0 -0
  81. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/providers/uitars/utils.py +0 -0
  82. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/telemetry.py +0 -0
  83. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/ui/__init__.py +0 -0
  84. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/ui/gradio/__init__.py +0 -0
  85. {cua_agent-0.1.41 → cua_agent-0.1.43}/agent/ui/gradio/app.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cua-agent
3
- Version: 0.1.41
3
+ Version: 0.1.43
4
4
  Summary: CUA (Computer Use) Agent for AI-driven computer interaction
5
5
  Author-Email: TryCua <gh@trycua.com>
6
6
  Requires-Python: >=3.10
@@ -131,6 +131,15 @@ class BaseLoop(ABC):
131
131
  An async generator that yields agent responses
132
132
  """
133
133
  raise NotImplementedError
134
+
135
+ @abstractmethod
136
+ async def cancel(self) -> None:
137
+ """Cancel the currently running agent loop task.
138
+
139
+ This method should stop any ongoing processing in the agent loop
140
+ and clean up resources appropriately.
141
+ """
142
+ raise NotImplementedError
134
143
 
135
144
  ###########################################
136
145
  # EXPERIMENT AND TRAJECTORY MANAGEMENT
@@ -101,6 +101,7 @@ class AnthropicLoop(BaseLoop):
101
101
  self.tool_manager = None
102
102
  self.callback_manager = None
103
103
  self.queue = asyncio.Queue() # Initialize queue
104
+ self.loop_task = None # Store the loop task for cancellation
104
105
 
105
106
  # Initialize handlers
106
107
  self.api_handler = AnthropicAPIHandler(self)
@@ -169,7 +170,7 @@ class AnthropicLoop(BaseLoop):
169
170
  logger.info("Client initialized successfully")
170
171
 
171
172
  # Start loop in background task
172
- loop_task = asyncio.create_task(self._run_loop(queue, messages))
173
+ self.loop_task = asyncio.create_task(self._run_loop(queue, messages))
173
174
 
174
175
  # Process and yield messages as they arrive
175
176
  while True:
@@ -184,7 +185,7 @@ class AnthropicLoop(BaseLoop):
184
185
  continue
185
186
 
186
187
  # Wait for loop to complete
187
- await loop_task
188
+ await self.loop_task
188
189
 
189
190
  # Send completion message
190
191
  yield {
@@ -200,6 +201,31 @@ class AnthropicLoop(BaseLoop):
200
201
  "content": f"Error: {str(e)}",
201
202
  "metadata": {"title": "❌ Error"},
202
203
  }
204
+
205
+ async def cancel(self) -> None:
206
+ """Cancel the currently running agent loop task.
207
+
208
+ This method stops the ongoing processing in the agent loop
209
+ by cancelling the loop_task if it exists and is running.
210
+ """
211
+ if self.loop_task and not self.loop_task.done():
212
+ logger.info("Cancelling Anthropic loop task")
213
+ self.loop_task.cancel()
214
+ try:
215
+ # Wait for the task to be cancelled with a timeout
216
+ await asyncio.wait_for(self.loop_task, timeout=2.0)
217
+ except asyncio.TimeoutError:
218
+ logger.warning("Timeout while waiting for loop task to cancel")
219
+ except asyncio.CancelledError:
220
+ logger.info("Loop task cancelled successfully")
221
+ except Exception as e:
222
+ logger.error(f"Error while cancelling loop task: {str(e)}")
223
+ finally:
224
+ # Put None in the queue to signal any waiting consumers to stop
225
+ await self.queue.put(None)
226
+ logger.info("Anthropic loop task cancelled")
227
+ else:
228
+ logger.info("No active Anthropic loop task to cancel")
203
229
 
204
230
  ###########################################
205
231
  # AGENT LOOP IMPLEMENTATION
@@ -105,6 +105,7 @@ class OmniLoop(BaseLoop):
105
105
  # Set API client attributes
106
106
  self.client = None
107
107
  self.retry_count = 0
108
+ self.loop_task = None # Store the loop task for cancellation
108
109
 
109
110
  # Initialize handlers
110
111
  self.api_handler = OmniAPIHandler(loop=self)
@@ -580,10 +581,55 @@ class OmniLoop(BaseLoop):
580
581
  Yields:
581
582
  Agent response format
582
583
  """
583
- # Initialize the message manager with the provided messages
584
- self.message_manager.messages = messages.copy()
585
- logger.info(f"Starting OmniLoop run with {len(self.message_manager.messages)} messages")
584
+ try:
585
+ logger.info(f"Starting OmniLoop run with {len(messages)} messages")
586
+
587
+ # Initialize the message manager with the provided messages
588
+ self.message_manager.messages = messages.copy()
589
+
590
+ # Create queue for response streaming
591
+ queue = asyncio.Queue()
592
+
593
+ # Start loop in background task
594
+ self.loop_task = asyncio.create_task(self._run_loop(queue, messages))
595
+
596
+ # Process and yield messages as they arrive
597
+ while True:
598
+ try:
599
+ item = await queue.get()
600
+ if item is None: # Stop signal
601
+ break
602
+ yield item
603
+ queue.task_done()
604
+ except Exception as e:
605
+ logger.error(f"Error processing queue item: {str(e)}")
606
+ continue
607
+
608
+ # Wait for loop to complete
609
+ await self.loop_task
586
610
 
611
+ # Send completion message
612
+ yield {
613
+ "role": "assistant",
614
+ "content": "Task completed successfully.",
615
+ "metadata": {"title": "✅ Complete"},
616
+ }
617
+
618
+ except Exception as e:
619
+ logger.error(f"Error in run method: {str(e)}")
620
+ yield {
621
+ "role": "assistant",
622
+ "content": f"Error: {str(e)}",
623
+ "metadata": {"title": "❌ Error"},
624
+ }
625
+
626
+ async def _run_loop(self, queue: asyncio.Queue, messages: List[Dict[str, Any]]) -> None:
627
+ """Internal method to run the agent loop with provided messages.
628
+
629
+ Args:
630
+ queue: Queue to put responses into
631
+ messages: List of messages in standard OpenAI format
632
+ """
587
633
  # Continue running until explicitly told to stop
588
634
  running = True
589
635
  turn_created = False
@@ -673,8 +719,8 @@ class OmniLoop(BaseLoop):
673
719
  # Log standardized response for ease of parsing
674
720
  self._log_api_call("agent_response", request=None, response=openai_compatible_response)
675
721
 
676
- # Yield the response to the caller
677
- yield openai_compatible_response
722
+ # Put the response in the queue
723
+ await queue.put(openai_compatible_response)
678
724
 
679
725
  # Check if we should continue this conversation
680
726
  running = should_continue
@@ -688,20 +734,47 @@ class OmniLoop(BaseLoop):
688
734
 
689
735
  except Exception as e:
690
736
  attempt += 1
691
- error_msg = f"Error in run method (attempt {attempt}/{max_attempts}): {str(e)}"
737
+ error_msg = f"Error in _run_loop method (attempt {attempt}/{max_attempts}): {str(e)}"
692
738
  logger.error(error_msg)
693
739
 
694
740
  # If this is our last attempt, provide more info about the error
695
741
  if attempt >= max_attempts:
696
742
  logger.error(f"Maximum retry attempts reached. Last error was: {str(e)}")
697
743
 
698
- yield {
699
- "error": str(e),
744
+ await queue.put({
745
+ "role": "assistant",
746
+ "content": f"Error: {str(e)}",
700
747
  "metadata": {"title": "❌ Error"},
701
- }
748
+ })
702
749
 
703
750
  # Create a brief delay before retrying
704
751
  await asyncio.sleep(1)
752
+ finally:
753
+ # Signal that we're done
754
+ await queue.put(None)
755
+
756
+ async def cancel(self) -> None:
757
+ """Cancel the currently running agent loop task.
758
+
759
+ This method stops the ongoing processing in the agent loop
760
+ by cancelling the loop_task if it exists and is running.
761
+ """
762
+ if self.loop_task and not self.loop_task.done():
763
+ logger.info("Cancelling Omni loop task")
764
+ self.loop_task.cancel()
765
+ try:
766
+ # Wait for the task to be cancelled with a timeout
767
+ await asyncio.wait_for(self.loop_task, timeout=2.0)
768
+ except asyncio.TimeoutError:
769
+ logger.warning("Timeout while waiting for loop task to cancel")
770
+ except asyncio.CancelledError:
771
+ logger.info("Loop task cancelled successfully")
772
+ except Exception as e:
773
+ logger.error(f"Error while cancelling loop task: {str(e)}")
774
+ finally:
775
+ logger.info("Omni loop task cancelled")
776
+ else:
777
+ logger.info("No active Omni loop task to cancel")
705
778
 
706
779
  async def process_model_response(self, response_text: str) -> Optional[Dict[str, Any]]:
707
780
  """Process model response to extract tool calls.
@@ -87,6 +87,7 @@ class OpenAILoop(BaseLoop):
87
87
  self.acknowledge_safety_check_callback = acknowledge_safety_check_callback
88
88
  self.queue = asyncio.Queue() # Initialize queue
89
89
  self.last_response_id = None # Store the last response ID across runs
90
+ self.loop_task = None # Store the loop task for cancellation
90
91
 
91
92
  # Initialize handlers
92
93
  self.api_handler = OpenAIAPIHandler(self)
@@ -132,28 +133,28 @@ class OpenAILoop(BaseLoop):
132
133
  logger.info("Starting OpenAI loop run")
133
134
 
134
135
  # Create queue for response streaming
135
- queue = asyncio.Queue()
136
+ self.queue = asyncio.Queue()
136
137
 
137
138
  # Ensure tool manager is initialized
138
139
  await self.tool_manager.initialize()
139
140
 
140
141
  # Start loop in background task
141
- loop_task = asyncio.create_task(self._run_loop(queue, messages))
142
+ self.loop_task = asyncio.create_task(self._run_loop(self.queue, messages))
142
143
 
143
144
  # Process and yield messages as they arrive
144
145
  while True:
145
146
  try:
146
- item = await queue.get()
147
+ item = await self.queue.get()
147
148
  if item is None: # Stop signal
148
149
  break
149
150
  yield item
150
- queue.task_done()
151
+ self.queue.task_done()
151
152
  except Exception as e:
152
153
  logger.error(f"Error processing queue item: {str(e)}")
153
154
  continue
154
155
 
155
156
  # Wait for loop to complete
156
- await loop_task
157
+ await self.loop_task
157
158
 
158
159
  # Send completion message
159
160
  yield {
@@ -169,6 +170,31 @@ class OpenAILoop(BaseLoop):
169
170
  "content": f"Error: {str(e)}",
170
171
  "metadata": {"title": "❌ Error"},
171
172
  }
173
+
174
+ async def cancel(self) -> None:
175
+ """Cancel the currently running agent loop task.
176
+
177
+ This method stops the ongoing processing in the agent loop
178
+ by cancelling the loop_task if it exists and is running.
179
+ """
180
+ if self.loop_task and not self.loop_task.done():
181
+ logger.info("Cancelling OpenAI loop task")
182
+ self.loop_task.cancel()
183
+ try:
184
+ # Wait for the task to be cancelled with a timeout
185
+ await asyncio.wait_for(self.loop_task, timeout=2.0)
186
+ except asyncio.TimeoutError:
187
+ logger.warning("Timeout while waiting for loop task to cancel")
188
+ except asyncio.CancelledError:
189
+ logger.info("Loop task cancelled successfully")
190
+ except Exception as e:
191
+ logger.error(f"Error while cancelling loop task: {str(e)}")
192
+ finally:
193
+ # Put None in the queue to signal any waiting consumers to stop
194
+ await self.queue.put(None)
195
+ logger.info("OpenAI loop task cancelled")
196
+ else:
197
+ logger.info("No active OpenAI loop task to cancel")
172
198
 
173
199
  ###########################################
174
200
  # AGENT LOOP IMPLEMENTATION
@@ -93,6 +93,7 @@ class UITARSLoop(BaseLoop):
93
93
  # Set API client attributes
94
94
  self.client = None
95
95
  self.retry_count = 0
96
+ self.loop_task = None # Store the loop task for cancellation
96
97
 
97
98
  # Initialize visualization helper
98
99
  self.viz_helper = VisualizationHelper(agent=self)
@@ -462,10 +463,55 @@ class UITARSLoop(BaseLoop):
462
463
  Yields:
463
464
  Agent response format
464
465
  """
465
- # Initialize the message manager with the provided messages
466
- self.message_manager.messages = messages.copy()
467
- logger.info(f"Starting UITARSLoop run with {len(self.message_manager.messages)} messages")
466
+ try:
467
+ logger.info(f"Starting UITARSLoop run with {len(messages)} messages")
468
+
469
+ # Initialize the message manager with the provided messages
470
+ self.message_manager.messages = messages.copy()
471
+
472
+ # Create queue for response streaming
473
+ queue = asyncio.Queue()
474
+
475
+ # Start loop in background task
476
+ self.loop_task = asyncio.create_task(self._run_loop(queue, messages))
477
+
478
+ # Process and yield messages as they arrive
479
+ while True:
480
+ try:
481
+ item = await queue.get()
482
+ if item is None: # Stop signal
483
+ break
484
+ yield item
485
+ queue.task_done()
486
+ except Exception as e:
487
+ logger.error(f"Error processing queue item: {str(e)}")
488
+ continue
489
+
490
+ # Wait for loop to complete
491
+ await self.loop_task
468
492
 
493
+ # Send completion message
494
+ yield {
495
+ "role": "assistant",
496
+ "content": "Task completed successfully.",
497
+ "metadata": {"title": "✅ Complete"},
498
+ }
499
+
500
+ except Exception as e:
501
+ logger.error(f"Error in run method: {str(e)}")
502
+ yield {
503
+ "role": "assistant",
504
+ "content": f"Error: {str(e)}",
505
+ "metadata": {"title": "❌ Error"},
506
+ }
507
+
508
+ async def _run_loop(self, queue: asyncio.Queue, messages: List[Dict[str, Any]]) -> None:
509
+ """Internal method to run the agent loop with provided messages.
510
+
511
+ Args:
512
+ queue: Queue to put responses into
513
+ messages: List of messages in standard OpenAI format
514
+ """
469
515
  # Continue running until explicitly told to stop
470
516
  running = True
471
517
  turn_created = False
@@ -475,88 +521,117 @@ class UITARSLoop(BaseLoop):
475
521
  attempt = 0
476
522
  max_attempts = 3
477
523
 
478
- while running and attempt < max_attempts:
479
- try:
480
- # Create a new turn directory if it's not already created
481
- if not turn_created:
482
- self._create_turn_dir()
483
- turn_created = True
524
+ try:
525
+ while running and attempt < max_attempts:
526
+ try:
527
+ # Create a new turn directory if it's not already created
528
+ if not turn_created:
529
+ self._create_turn_dir()
530
+ turn_created = True
484
531
 
485
- # Ensure client is initialized
486
- if self.client is None:
487
- logger.info("Initializing client...")
488
- await self.initialize_client()
532
+ # Ensure client is initialized
489
533
  if self.client is None:
490
- raise RuntimeError("Failed to initialize client")
491
- logger.info("Client initialized successfully")
492
-
493
- # Get current screen
494
- base64_screenshot = await self._get_current_screen()
495
-
496
- # Add screenshot to message history
497
- self.message_manager.add_user_message(
498
- [
499
- {
500
- "type": "image_url",
501
- "image_url": {"url": f"data:image/png;base64,{base64_screenshot}"},
502
- }
503
- ]
504
- )
505
- logger.info("Added screenshot to message history")
534
+ logger.info("Initializing client...")
535
+ await self.initialize_client()
536
+ if self.client is None:
537
+ raise RuntimeError("Failed to initialize client")
538
+ logger.info("Client initialized successfully")
539
+
540
+ # Get current screen
541
+ base64_screenshot = await self._get_current_screen()
542
+
543
+ # Add screenshot to message history
544
+ self.message_manager.add_user_message(
545
+ [
546
+ {
547
+ "type": "image_url",
548
+ "image_url": {"url": f"data:image/png;base64,{base64_screenshot}"},
549
+ }
550
+ ]
551
+ )
552
+ logger.info("Added screenshot to message history")
506
553
 
507
- # Get system prompt
508
- system_prompt = self._get_system_prompt()
554
+ # Get system prompt
555
+ system_prompt = self._get_system_prompt()
509
556
 
510
- # Make API call with retries
511
- response = await self._make_api_call(
512
- self.message_manager.messages, system_prompt
513
- )
557
+ # Make API call with retries
558
+ response = await self._make_api_call(
559
+ self.message_manager.messages, system_prompt
560
+ )
514
561
 
515
- # Handle the response (may execute actions)
516
- # Returns: (should_continue, action_screenshot_saved)
517
- should_continue, new_screenshot_saved = await self._handle_response(
518
- response, self.message_manager.messages
519
- )
562
+ # Handle the response (may execute actions)
563
+ # Returns: (should_continue, action_screenshot_saved)
564
+ should_continue, new_screenshot_saved = await self._handle_response(
565
+ response, self.message_manager.messages
566
+ )
520
567
 
521
- # Update whether an action screenshot was saved this turn
522
- action_screenshot_saved = action_screenshot_saved or new_screenshot_saved
523
-
524
- agent_response = await to_agent_response_format(
525
- response,
526
- messages,
527
- model=self.model,
528
- )
529
- # Log standardized response for ease of parsing
530
- self._log_api_call("agent_response", request=None, response=agent_response)
531
- yield agent_response
532
-
533
- # Check if we should continue this conversation
534
- running = should_continue
568
+ # Update whether an action screenshot was saved this turn
569
+ action_screenshot_saved = action_screenshot_saved or new_screenshot_saved
570
+
571
+ agent_response = await to_agent_response_format(
572
+ response,
573
+ messages,
574
+ model=self.model,
575
+ )
576
+ # Log standardized response for ease of parsing
577
+ self._log_api_call("agent_response", request=None, response=agent_response)
578
+
579
+ # Put the response in the queue
580
+ await queue.put(agent_response)
581
+
582
+ # Check if we should continue this conversation
583
+ running = should_continue
535
584
 
536
- # Create a new turn directory if we're continuing
537
- if running:
538
- turn_created = False
585
+ # Create a new turn directory if we're continuing
586
+ if running:
587
+ turn_created = False
539
588
 
540
- # Reset attempt counter on success
541
- attempt = 0
589
+ # Reset attempt counter on success
590
+ attempt = 0
542
591
 
592
+ except Exception as e:
593
+ attempt += 1
594
+ error_msg = f"Error in run method (attempt {attempt}/{max_attempts}): {str(e)}"
595
+ logger.error(error_msg)
596
+
597
+ # If this is our last attempt, provide more info about the error
598
+ if attempt >= max_attempts:
599
+ logger.error(f"Maximum retry attempts reached. Last error was: {str(e)}")
600
+
601
+ await queue.put({
602
+ "role": "assistant",
603
+ "content": f"Error: {str(e)}",
604
+ "metadata": {"title": "❌ Error"},
605
+ })
606
+
607
+ # Create a brief delay before retrying
608
+ await asyncio.sleep(1)
609
+ finally:
610
+ # Signal that we're done
611
+ await queue.put(None)
612
+
613
+ async def cancel(self) -> None:
614
+ """Cancel the currently running agent loop task.
615
+
616
+ This method stops the ongoing processing in the agent loop
617
+ by cancelling the loop_task if it exists and is running.
618
+ """
619
+ if self.loop_task and not self.loop_task.done():
620
+ logger.info("Cancelling UITARS loop task")
621
+ self.loop_task.cancel()
622
+ try:
623
+ # Wait for the task to be cancelled with a timeout
624
+ await asyncio.wait_for(self.loop_task, timeout=2.0)
625
+ except asyncio.TimeoutError:
626
+ logger.warning("Timeout while waiting for loop task to cancel")
627
+ except asyncio.CancelledError:
628
+ logger.info("Loop task cancelled successfully")
543
629
  except Exception as e:
544
- attempt += 1
545
- error_msg = f"Error in run method (attempt {attempt}/{max_attempts}): {str(e)}"
546
- logger.error(error_msg)
547
-
548
- # If this is our last attempt, provide more info about the error
549
- if attempt >= max_attempts:
550
- logger.error(f"Maximum retry attempts reached. Last error was: {str(e)}")
551
-
552
- yield {
553
- "role": "assistant",
554
- "content": f"Error: {str(e)}",
555
- "metadata": {"title": "❌ Error"},
556
- }
557
-
558
- # Create a brief delay before retrying
559
- await asyncio.sleep(1)
630
+ logger.error(f"Error while cancelling loop task: {str(e)}")
631
+ finally:
632
+ logger.info("UITARS loop task cancelled")
633
+ else:
634
+ logger.info("No active UITARS loop task to cancel")
560
635
 
561
636
  ###########################################
562
637
  # UTILITY METHODS
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "cua-agent"
9
- version = "0.1.41"
9
+ version = "0.1.43"
10
10
  description = "CUA (Computer Use) Agent for AI-driven computer interaction"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -109,7 +109,7 @@ target-version = [
109
109
 
110
110
  [tool.ruff]
111
111
  line-length = 100
112
- target-version = "0.1.41"
112
+ target-version = "0.1.43"
113
113
  select = [
114
114
  "E",
115
115
  "F",
@@ -123,7 +123,7 @@ docstring-code-format = true
123
123
 
124
124
  [tool.mypy]
125
125
  strict = true
126
- python_version = "0.1.41"
126
+ python_version = "0.1.43"
127
127
  ignore_missing_imports = true
128
128
  disallow_untyped_defs = true
129
129
  check_untyped_defs = true
File without changes
File without changes