lybic-guiagents 0.3.0__py3-none-any.whl → 0.5.0__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.

Potentially problematic release.


This version of lybic-guiagents might be problematic. Click here for more details.

gui_agents/grpc_app.py CHANGED
@@ -29,6 +29,7 @@ import platform
29
29
  from concurrent import futures
30
30
  import grpc
31
31
  import uuid
32
+ import datetime
32
33
 
33
34
  from lybic import LybicClient, LybicAuth, Sandbox
34
35
  import gui_agents.cli_app as app
@@ -44,20 +45,19 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
44
45
  Implements the Agent gRPC service.
45
46
  """
46
47
 
47
- def __init__(self, max_concurrent_task_num = 1):
48
+ def __init__(self, max_concurrent_task_num: int = 1, log_dir: str = "runtime"):
48
49
  """
49
50
  Initialize the AgentServicer with concurrency and runtime state.
50
51
 
51
52
  Parameters:
52
53
  max_concurrent_task_num (int): Maximum number of agent tasks allowed to run concurrently; defaults to 1.
54
+ log_dir (str): Directory for logging and task-related files.
53
55
  """
54
- self.lybic_auth: LybicAuth | None = None
55
56
  self.max_concurrent_task_num = max_concurrent_task_num
56
57
  self.tasks = {}
57
58
  self.global_common_config = agent_pb2.CommonConfig(id="global")
58
59
  self.task_lock = asyncio.Lock()
59
- self.lybic_client: LybicClient | None = None
60
- self.sandbox: Sandbox | None = None
60
+ self.log_dir = log_dir
61
61
 
62
62
  async def GetAgentTaskStream(self, request, context):
63
63
  """
@@ -110,18 +110,55 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
110
110
  domain=platform.node(),
111
111
  )
112
112
 
113
+ def _setup_task_state(self, task_id: str) -> Registry:
114
+ """Setup global state and registry for task execution with task isolation"""
115
+ # Create timestamp-based directory structure like cli_app.py
116
+ datetime_str = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
117
+ timestamp_dir = Path(self.log_dir) / f"{datetime_str}_{task_id[:8]}" # Include task_id prefix
118
+ cache_dir = timestamp_dir / "cache" / "screens"
119
+ state_dir = timestamp_dir / "state"
120
+
121
+ cache_dir.mkdir(parents=True, exist_ok=True)
122
+ state_dir.mkdir(parents=True, exist_ok=True)
123
+
124
+ # Create task-specific registry
125
+ task_registry = Registry()
126
+
127
+ # Register global state for this task in task-specific registry
128
+ global_state = GlobalState(
129
+ screenshot_dir=str(cache_dir),
130
+ tu_path=str(state_dir / "tu.json"),
131
+ search_query_path=str(state_dir / "search_query.json"),
132
+ completed_subtasks_path=str(state_dir / "completed_subtasks.json"),
133
+ failed_subtasks_path=str(state_dir / "failed_subtasks.json"),
134
+ remaining_subtasks_path=str(state_dir / "remaining_subtasks.json"),
135
+ termination_flag_path=str(state_dir / "termination_flag.json"),
136
+ running_state_path=str(state_dir / "running_state.json"),
137
+ display_info_path=str(timestamp_dir / "display.json"),
138
+ agent_log_path=str(timestamp_dir / "agent_log.json")
139
+ )
140
+
141
+ # Register in task-specific registry using instance method
142
+ registry_key = "GlobalStateStore"
143
+ task_registry.register_instance(registry_key, global_state)
144
+
145
+ logger.info(f"Created task-specific registry for task {task_id}")
146
+
147
+ return task_registry
148
+
113
149
  async def _run_task(self, task_id: str, backend_kwargs):
114
150
  """
115
151
  Run the lifecycle of a single agent task: mark it running, execute the agent, record final state, emit stream messages, and unregister the task.
116
-
152
+
117
153
  Parameters:
118
154
  task_id (str): Identifier of the task to run.
119
155
  backend_kwargs (dict): Backend configuration passed to the HardwareInterface (e.g., platform, org/api fields, sandbox id).
120
-
156
+
121
157
  Notes:
122
158
  - Updates the task entry in self.tasks (status and final_state).
123
159
  - Emits task lifecycle messages via stream_manager and unregisters the task when finished.
124
160
  - Exceptions are caught, the task status is set to "error", and an error message is emitted.
161
+ - Supports task cancellation via asyncio.CancelledError.
125
162
  """
126
163
  async with self.task_lock:
127
164
  self.tasks[task_id]["status"] = "running"
@@ -136,23 +173,29 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
136
173
  # Send message through stream manager
137
174
  await stream_manager.add_message(task_id, "starting", "Task starting")
138
175
 
139
- # Set task_id for the agent to enable streaming from within the agent
176
+ # Create task-specific registry
177
+ task_registry = self._setup_task_state(task_id)
178
+
179
+ # Set task_id for the agent. This is needed so that agent.reset() can find the right components.
140
180
  if hasattr(agent, 'set_task_id'):
141
181
  agent.set_task_id(task_id)
142
182
 
143
183
  hwi = HardwareInterface(backend='lybic', **backend_kwargs)
144
184
 
185
+ # We need to set the registry for the main thread context before reset
186
+ Registry.set_task_registry(task_id, task_registry)
145
187
  agent.reset()
188
+ Registry.remove_task_registry(task_id) # Clean up main thread's local
146
189
 
147
- # Run the blocking function in a separate thread to avoid blocking the event loop
148
- mode: InstanceMode | None = backend_kwargs["mode"]
190
+ # Run the blocking function in a separate thread, passing the context
191
+ mode: InstanceMode | None = backend_kwargs.get("mode")
149
192
  if mode and mode == InstanceMode.NORMAL:
150
- await asyncio.to_thread(app.run_agent_normal,agent, query, hwi, steps, False)
193
+ await asyncio.to_thread(app.run_agent_normal, agent, query, hwi, steps, False, task_id=task_id, task_registry=task_registry)
151
194
  else:
152
- await asyncio.to_thread(app.run_agent_fast, agent, query, hwi, steps, False)
195
+ await asyncio.to_thread(app.run_agent_fast, agent, query, hwi, steps, False, task_id=task_id, task_registry=task_registry)
153
196
 
154
- global_state: GlobalState = Registry.get("GlobalStateStore") # type: ignore
155
- final_state = global_state.get_running_state()
197
+ # The final state is now determined inside the thread. We'll assume success if no exception.
198
+ final_state = "completed"
156
199
 
157
200
  async with self.task_lock:
158
201
  self.tasks[task_id]["final_state"] = final_state
@@ -164,6 +207,11 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
164
207
  status = final_state if final_state else 'unknown'
165
208
  await stream_manager.add_message(task_id, "finished", f"Task finished with status: {status}")
166
209
 
210
+ except asyncio.CancelledError:
211
+ logger.info(f"Task {task_id} was cancelled")
212
+ async with self.task_lock:
213
+ self.tasks[task_id]["status"] = "cancelled"
214
+ await stream_manager.add_message(task_id, "cancelled", "Task was cancelled by user request")
167
215
  except Exception as e:
168
216
  logger.exception(f"Error during task execution for {task_id}: {e}")
169
217
  async with self.task_lock:
@@ -171,6 +219,7 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
171
219
  await stream_manager.add_message(task_id, "error", f"An error occurred: {e}")
172
220
  finally:
173
221
  logger.info(f"Task {task_id} processing finished.")
222
+ # Registry cleanup is now handled within the worker thread
174
223
  await stream_manager.unregister_task(task_id)
175
224
 
176
225
  async def _make_backend_kwargs(self, request):
@@ -190,7 +239,6 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
190
239
  - "endpoint": Lybic API endpoint.
191
240
 
192
241
  Side effects:
193
- - May initialize or replace self.lybic_auth from request.runningConfig.authorizationInfo.
194
242
  - May call self._create_sandbox(...) to create or retrieve a sandbox and determine the platform.
195
243
  """
196
244
  backend_kwargs = {}
@@ -203,33 +251,46 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
203
251
  if request.HasField("runningConfig"):
204
252
  if request.runningConfig.backend:
205
253
  backend = request.runningConfig.backend
206
- if request.runningConfig.HasField("authorizationInfo"):
207
- self.lybic_auth = LybicAuth(
208
- org_id=request.runningConfig.authorizationInfo.orgID,
209
- api_key=request.runningConfig.authorizationInfo.apiKey,
210
- endpoint=request.runningConfig.authorizationInfo.apiEndpoint or "https://api.lybic.cn/"
211
- )
212
254
  backend_kwargs["mode"] = request.runningConfig.mode
213
255
 
214
256
  platform_str = platform.system()
215
257
  sid = ''
258
+ sandbox_pb = None
259
+
216
260
  if backend == 'lybic':
261
+ auth_info = (request.runningConfig.authorizationInfo
262
+ if request.HasField("runningConfig") and request.runningConfig.HasField("authorizationInfo")
263
+ else self.global_common_config.authorizationInfo)
264
+ if not auth_info or not auth_info.orgID or not auth_info.apiKey:
265
+ raise ValueError("Lybic backend requires valid authorization (orgID and apiKey)")
266
+
267
+ lybic_auth = LybicAuth(
268
+ org_id=auth_info.orgID,
269
+ api_key=auth_info.apiKey,
270
+ endpoint=auth_info.apiEndpoint or "https://api.lybic.cn/"
271
+ )
272
+
217
273
  if request.HasField("sandbox"):
218
274
  shape = request.sandbox.shapeName
219
275
  sid = request.sandbox.id
220
276
  if sid:
221
277
  logger.info(f"Using existing sandbox with id: {sid}")
278
+ sandbox_pb = await self._get_sandbox_pb(sid, lybic_auth) # if not exist raise NotFound
279
+ platform_str = platform_map.get(sandbox_pb.os, platform.system())
222
280
  else:
223
- sid, platform_str = await self._create_sandbox(shape)
281
+ sandbox_pb = await self._create_sandbox(shape, lybic_auth)
282
+ sid, platform_str = sandbox_pb.id, platform_map.get(sandbox_pb.os, platform.system())
224
283
 
225
284
  if request.sandbox.os != agent_pb2.SandboxOS.OSUNDEFINED:
226
285
  platform_str = platform_map.get(request.sandbox.os, platform.system())
227
286
  else:
228
- sid, platform_str = await self._create_sandbox(shape)
229
-
287
+ sandbox_pb = await self._create_sandbox(shape, lybic_auth)
288
+ sid, platform_str = sandbox_pb.id, platform_map.get(sandbox_pb.os, platform.system())
230
289
  else:
231
- platform_str = platform_map.get(request.sandbox.os, platform.system())
290
+ if request.HasField("sandbox") and request.sandbox.os != agent_pb2.SandboxOS.OSUNDEFINED:
291
+ platform_str = platform_map.get(request.sandbox.os, platform.system())
232
292
 
293
+ backend_kwargs["sandbox"] = sandbox_pb
233
294
  backend_kwargs["platform"] = platform_str
234
295
  backend_kwargs["precreate_sid"] = sid
235
296
 
@@ -250,12 +311,6 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
250
311
  return backend_kwargs
251
312
 
252
313
  async def _make_agent(self,request):
253
- # todo: add max_steps support
254
- # max_steps = 50
255
- # if request.HasField("runningConfig") and request.runningConfig.steps:
256
- # max_steps = request.runningConfig.steps
257
-
258
- # Dynamically build tools_dict based on global config
259
314
  """
260
315
  Builds and returns an AgentS2 configured for the incoming request by applying model and provider overrides to the tool configurations.
261
316
 
@@ -345,7 +400,6 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
345
400
  tools_config=tools_config,
346
401
  )
347
402
 
348
-
349
403
  async def RunAgentInstruction(self, request, context):
350
404
  """
351
405
  Stream task progress for a newly created instruction-run agent while managing task lifecycle and concurrency.
@@ -390,8 +444,13 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
390
444
  "query": request.instruction,
391
445
  "agent": agent,
392
446
  "max_steps": max_steps,
447
+ "sandbox": backend_kwargs["sandbox"],
393
448
  }
394
449
 
450
+ # This property is used to pass sandbox information.
451
+ # It has now completed its mission and needs to be deleted, otherwise other backends may crash.
452
+ del backend_kwargs["sandbox"]
453
+
395
454
  task_future = asyncio.create_task(self._run_task(task_id, backend_kwargs))
396
455
  self.tasks[task_id]["future"] = task_future
397
456
  try:
@@ -406,6 +465,16 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
406
465
  logger.info(f"RunAgentInstruction stream for {task_id} cancelled by client.")
407
466
  if task_future:
408
467
  task_future.cancel()
468
+ # Set cancellation flag in global state for agents to check
469
+ try:
470
+ global_state: GlobalState = Registry.get_from_context("GlobalStateStore", task_id)
471
+ if global_state:
472
+ global_state.set_running_state("cancelled")
473
+ logger.info(f"Set running state to 'cancelled' for task {task_id} due to client disconnect.")
474
+ else:
475
+ logger.warning(f"Could not find GlobalState for task {task_id} to set cancellation flag on client disconnect.")
476
+ except Exception as e:
477
+ logger.error(f"Error setting cancellation flag for task {task_id} on client disconnect: {e}")
409
478
  except Exception as e:
410
479
  logger.exception(f"Error in RunAgentInstruction stream for task {task_id}")
411
480
  context.set_code(grpc.StatusCode.INTERNAL)
@@ -449,7 +518,11 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
449
518
  "query": request.instruction,
450
519
  "agent": agent,
451
520
  "max_steps": max_steps,
521
+ "sandbox": backend_kwargs["sandbox"],
452
522
  }
523
+ # This property is used to pass sandbox information.
524
+ # It has now completed its mission and needs to be deleted, otherwise other backends may crash.
525
+ del backend_kwargs["sandbox"]
453
526
 
454
527
  # Start the task in background
455
528
  task_future = asyncio.create_task(self._run_task(task_id, backend_kwargs))
@@ -490,6 +563,7 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
490
563
  "running": agent_pb2.TaskStatus.RUNNING,
491
564
  "fulfilled": agent_pb2.TaskStatus.SUCCESS,
492
565
  "rejected": agent_pb2.TaskStatus.FAILURE,
566
+ "cancelled": agent_pb2.TaskStatus.CANCELLED,
493
567
  }
494
568
 
495
569
  if status == "finished":
@@ -500,6 +574,10 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
500
574
  task_status = agent_pb2.TaskStatus.FAILURE
501
575
  message = "Task failed with an exception."
502
576
  result = ""
577
+ elif status == "cancelled":
578
+ task_status = agent_pb2.TaskStatus.CANCELLED
579
+ message = "Task was cancelled by user request."
580
+ result = ""
503
581
  else: # pending or running
504
582
  task_status = status_map.get(status, agent_pb2.TaskStatus.TASKSTATUSUNDEFINED)
505
583
  message = "Task is running."
@@ -510,9 +588,85 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
510
588
  status=task_status,
511
589
  message=message,
512
590
  result=result,
513
- sandbox=task_info["request"].sandbox
591
+ sandbox=task_info["sandbox"]
514
592
  )
515
593
 
594
+ async def CancelTask(self, request, context):
595
+ """
596
+ Cancel a running task by its taskId.
597
+
598
+ If the task exists and is running, it will be cancelled and a success response is returned.
599
+ If the task is not found or already completed, an appropriate response is returned.
600
+
601
+ Parameters:
602
+ request: CancelTaskRequest containing the taskId to cancel
603
+ context: gRPC context for setting status codes and details
604
+
605
+ Returns:
606
+ CancelTaskResponse: Response containing taskId, success status, and message
607
+ """
608
+ task_id = request.taskId
609
+ logger.info(f"Received CancelTask request for taskId: {task_id}")
610
+
611
+ async with self.task_lock:
612
+ task_info = self.tasks.get(task_id)
613
+
614
+ if not task_info:
615
+ return agent_pb2.CancelTaskResponse(
616
+ taskId=task_id,
617
+ success=False,
618
+ message=f"Task with ID {task_id} not found."
619
+ )
620
+
621
+ status = task_info["status"]
622
+ task_future = task_info.get("future")
623
+
624
+ # Check if task can be cancelled
625
+ if status in ["finished", "error"]:
626
+ return agent_pb2.CancelTaskResponse(
627
+ taskId=task_id,
628
+ success=False,
629
+ message=f"Task {task_id} is already {status} and cannot be cancelled."
630
+ )
631
+ elif status == "cancelled":
632
+ return agent_pb2.CancelTaskResponse(
633
+ taskId=task_id,
634
+ success=True,
635
+ message=f"Task {task_id} was already cancelled."
636
+ )
637
+ elif status in ["pending", "running"] and task_future:
638
+ try:
639
+ # Cancel the task future
640
+ task_future.cancel()
641
+ task_info["status"] = "cancelled"
642
+
643
+ # Set cancellation flag in global state for agents to check
644
+ global_state: GlobalState = Registry.get_from_context("GlobalStateStore", task_id) # type: ignore
645
+ global_state.set_running_state("cancelled")
646
+
647
+ # Send cancellation message through stream manager
648
+ await stream_manager.add_message(task_id, "cancelled", "Task was cancelled by user request")
649
+
650
+ logger.info(f"Task {task_id} successfully cancelled")
651
+ return agent_pb2.CancelTaskResponse(
652
+ taskId=task_id,
653
+ success=True,
654
+ message=f"Task {task_id} has been successfully cancelled."
655
+ )
656
+ except Exception as e:
657
+ logger.error(f"Failed to cancel task {task_id}: {e}")
658
+ return agent_pb2.CancelTaskResponse(
659
+ taskId=task_id,
660
+ success=False,
661
+ message=f"Failed to cancel task {task_id}: {e}"
662
+ )
663
+ else:
664
+ return agent_pb2.CancelTaskResponse(
665
+ taskId=task_id,
666
+ success=False,
667
+ message=f"Task {task_id} is in state '{status}' and cannot be cancelled."
668
+ )
669
+
516
670
  def _mask_config_secrets(self, config: CommonConfig) -> CommonConfig:
517
671
  """
518
672
  Return a deep copy of a CommonConfig with sensitive API keys replaced by "********".
@@ -615,20 +769,17 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
615
769
  context.set_details(f"Config for task {request.id} not found.")
616
770
  return agent_pb2.CommonConfig()
617
771
 
618
- async def _new_lybic_client(self):
772
+ def _new_lybic_client(self, lybic_auth: LybicAuth) -> LybicClient:
619
773
  """
620
- Create and store a new LybicClient using the servicer's current LybicAuth.
621
-
622
- This replaces the servicer's `lybic_client` attribute with a newly constructed
623
- LybicClient initialized from `self.lybic_auth`.
774
+ Create and return a new LybicClient.
624
775
  """
625
- self.lybic_client = LybicClient(self.lybic_auth)
776
+ return LybicClient(lybic_auth)
626
777
 
627
778
  async def SetGlobalCommonConfig(self, request, context):
628
779
  """
629
- Set the server's global common configuration and initialize Lybic authorization if provided.
780
+ Set the server's global common configuration.
630
781
 
631
- Sets request.commonConfig.id to "global" and stores it as the servicer's global_common_config. If the provided config contains authorizationInfo, initializes or updates self.lybic_auth with the org ID, API key, and API endpoint (defaulting to "https://api.lybic.cn/" when endpoint is empty).
782
+ Sets request.commonConfig.id to "global" and stores it as the servicer's global_common_config.
632
783
 
633
784
  Parameters:
634
785
  request: gRPC request containing `commonConfig` to apply.
@@ -636,17 +787,14 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
636
787
  Returns:
637
788
  agent_pb2.SetCommonConfigResponse: Response with `success=True` and the configuration `id`.
638
789
  """
790
+ if os.environ.get("ALLOW_SET_GLOBAL_CONFIG", "0")=="0":
791
+ context.set_code(grpc.StatusCode.PERMISSION_DENIED)
792
+ context.set_details("Permission denied.")
793
+ return agent_pb2.SetCommonConfigResponse()
639
794
  logger.info("Setting new global common config.")
640
795
  request.commonConfig.id = "global"
641
796
  self.global_common_config = request.commonConfig
642
797
 
643
- if self.global_common_config.HasField("authorizationInfo"): # lybic
644
- self.lybic_auth = LybicAuth(
645
- org_id=self.global_common_config.authorizationInfo.orgID,
646
- api_key=self.global_common_config.authorizationInfo.apiKey,
647
- endpoint=self.global_common_config.authorizationInfo.apiEndpoint or "https://api.lybic.cn/"
648
- )
649
-
650
798
  return agent_pb2.SetCommonConfigResponse(success=True, id=self.global_common_config.id)
651
799
 
652
800
  async def SetGlobalCommonLLMConfig(self, request, context):
@@ -658,6 +806,10 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
658
806
  Returns:
659
807
  llmConfig: The `LLMConfig` message that was stored in the global configuration.
660
808
  """
809
+ if os.environ.get("ALLOW_SET_GLOBAL_CONFIG", "0")=="0":
810
+ context.set_code(grpc.StatusCode.PERMISSION_DENIED)
811
+ context.set_details("Permission denied.")
812
+ return agent_pb2.LLMConfig()
661
813
  if not self.global_common_config.HasField("stageModelConfig"):
662
814
  self.global_common_config.stageModelConfig.SetInParent()
663
815
  self.global_common_config.stageModelConfig.actionGeneratorModel.CopyFrom(request.llmConfig)
@@ -678,6 +830,10 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
678
830
  Returns:
679
831
  LLMConfig: The `llmConfig` that was applied.
680
832
  """
833
+ if os.environ.get("ALLOW_SET_GLOBAL_CONFIG", "0")=="0":
834
+ context.set_code(grpc.StatusCode.PERMISSION_DENIED)
835
+ context.set_details("Permission denied.")
836
+ return agent_pb2.LLMConfig()
681
837
  if not self.global_common_config.HasField("stageModelConfig"):
682
838
  self.global_common_config.stageModelConfig.SetInParent()
683
839
  self.global_common_config.stageModelConfig.groundingModel.CopyFrom(request.llmConfig)
@@ -694,34 +850,79 @@ class AgentServicer(agent_pb2_grpc.AgentServicer):
694
850
  Returns:
695
851
  The `llmConfig` that was set as the global embedding model.
696
852
  """
853
+ if os.environ.get("ALLOW_SET_GLOBAL_CONFIG", "0")=="0":
854
+ context.set_code(grpc.StatusCode.PERMISSION_DENIED)
855
+ context.set_details("Permission denied.")
856
+ return agent_pb2.LLMConfig()
697
857
  if not self.global_common_config.HasField("stageModelConfig"):
698
858
  self.global_common_config.stageModelConfig.SetInParent()
699
859
  self.global_common_config.stageModelConfig.embeddingModel.CopyFrom(request.llmConfig)
700
860
  logger.info(f"Global embedding LLM config updated to: {request.llmConfig.modelName}")
701
861
  return request.llmConfig
702
862
 
703
- async def _create_sandbox(self,shape:str):
863
+ async def _create_sandbox(self, shape: str, lybic_auth: LybicAuth) -> agent_pb2.Sandbox:
704
864
  """
705
865
  Create a sandbox with the given shape via the Lybic service and return its identifier and operating system.
706
-
866
+
707
867
  Parameters:
708
868
  shape (str): The sandbox shape to create (provider-specific size/OS configuration).
709
-
869
+ lybic_auth (LybicAuth): The authentication object for Lybic.
870
+
710
871
  Returns:
711
- tuple: A pair (sandbox_id, platform_os) where `sandbox_id` is the created sandbox identifier and `platform_os` is the sandbox operating system string.
712
-
713
- Raises:
714
- Exception: If Lybic authorization is not initialized (call SetGlobalCommonConfig first).
872
+ agent_pb2.Sandbox: A protobuf message containing sandbox details.
873
+ """
874
+ lybic_client = self._new_lybic_client(lybic_auth)
875
+ sandbox_service = Sandbox(lybic_client)
876
+ result = await sandbox_service.create(shape=shape)
877
+ sandbox = await sandbox_service.get(result.id)
878
+ await lybic_client.close()
879
+
880
+ return agent_pb2.Sandbox(
881
+ id=sandbox.sandbox.id,
882
+ os=self._lybic_sandbox_os_to_pb_enum(sandbox.sandbox.shape),
883
+ shapeName=sandbox.sandbox.shapeName,
884
+ hardwareAcceleratedEncoding=sandbox.sandbox.shape.hardwareAcceleratedEncoding,
885
+ virtualization=sandbox.sandbox.shape.virtualization,
886
+ architecture=sandbox.sandbox.shape.architecture,
887
+ )
888
+
889
+ @staticmethod
890
+ def _lybic_sandbox_os_to_pb_enum(os) -> agent_pb2.SandboxOS:
891
+ """
892
+ Converts a sandbox OS string to an enum value.
893
+ """
894
+ os_raw = getattr(os, "os", "") or ""
895
+ os_upper = str(os_raw).upper()
896
+ if "WIN" in os_upper:
897
+ os_enum = agent_pb2.SandboxOS.WINDOWS
898
+ elif "LINUX" in os_upper or "UBUNTU" in os_upper:
899
+ os_enum = agent_pb2.SandboxOS.LINUX
900
+ elif "ANDROID" in os_upper:
901
+ os_enum = agent_pb2.SandboxOS.ANDROID
902
+ else:
903
+ os_enum = agent_pb2.SandboxOS.OSUNDEFINED
904
+ return os_enum
905
+
906
+ async def _get_sandbox_pb(self, sid: str, lybic_auth: LybicAuth) -> agent_pb2.Sandbox:
907
+ """
908
+ Retrieves sandbox details for a given sandbox ID and returns them as a protobuf message.
715
909
  """
716
- if not self.lybic_auth:
717
- raise Exception("Lybic client not initialized. Please call SetGlobalCommonConfig before")
910
+ if not lybic_auth:
911
+ raise ValueError("Lybic client not initialized. Please call SetGlobalCommonConfig before")
718
912
 
719
- await self._new_lybic_client()
720
- if not self.sandbox:
721
- self.sandbox = Sandbox(self.lybic_client)
722
- result = await self.sandbox.create(shape=shape)
723
- sandbox = await self.sandbox.get(result.id)
724
- return sandbox.sandbox.id, sandbox.sandbox.shape.os
913
+ lybic_client = self._new_lybic_client(lybic_auth)
914
+ sandbox_service = Sandbox(lybic_client)
915
+ sandbox_details = await sandbox_service.get(sid)
916
+ await lybic_client.close()
917
+
918
+ return agent_pb2.Sandbox(
919
+ id=sandbox_details.sandbox.id,
920
+ os=self._lybic_sandbox_os_to_pb_enum(sandbox_details.sandbox.shape),
921
+ shapeName=sandbox_details.sandbox.shapeName,
922
+ hardwareAcceleratedEncoding=sandbox_details.sandbox.shape.hardwareAcceleratedEncoding,
923
+ virtualization=sandbox_details.sandbox.shape.virtualization,
924
+ architecture=sandbox_details.sandbox.shape.architecture,
925
+ )
725
926
 
726
927
  async def serve():
727
928
  """
@@ -735,8 +936,8 @@ async def serve():
735
936
  """
736
937
  port = os.environ.get("GRPC_PORT", 50051)
737
938
  max_workers = int(os.environ.get("GRPC_MAX_WORKER_THREADS", 100))
738
- # task_num = int(os.environ.get("TASK_MAX_TASKS", 5))
739
- servicer = AgentServicer(max_concurrent_task_num=1)
939
+ task_num = int(os.environ.get("TASK_MAX_TASKS", 5))
940
+ servicer = AgentServicer(max_concurrent_task_num=task_num, log_dir=app.log_dir)
740
941
  server = grpc.aio.server(futures.ThreadPoolExecutor(max_workers))
741
942
  agent_pb2_grpc.add_AgentServicer_to_server(servicer, server)
742
943
  server.add_insecure_port(f'[::]:{port}')
@@ -752,32 +953,6 @@ def main():
752
953
  has_display, pyautogui_available, _ = app.check_display_environment()
753
954
  compatible_backends, incompatible_backends = app.get_compatible_backends(has_display, pyautogui_available)
754
955
  app.validate_backend_compatibility('lybic', compatible_backends, incompatible_backends)
755
- timestamp_dir = os.path.join(app.log_dir, app.datetime_str)
756
- cache_dir = os.path.join(timestamp_dir, "cache", "screens")
757
- state_dir = os.path.join(timestamp_dir, "state")
758
-
759
- os.makedirs(cache_dir, exist_ok=True)
760
- os.makedirs(state_dir, exist_ok=True)
761
-
762
- Registry.register(
763
- "GlobalStateStore",
764
- GlobalState(
765
- screenshot_dir=cache_dir,
766
- tu_path=os.path.join(state_dir, "tu.json"),
767
- search_query_path=os.path.join(state_dir, "search_query.json"),
768
- completed_subtasks_path=os.path.join(state_dir,
769
- "completed_subtasks.json"),
770
- failed_subtasks_path=os.path.join(state_dir,
771
- "failed_subtasks.json"),
772
- remaining_subtasks_path=os.path.join(state_dir,
773
- "remaining_subtasks.json"),
774
- termination_flag_path=os.path.join(state_dir,
775
- "termination_flag.json"),
776
- running_state_path=os.path.join(state_dir, "running_state.json"),
777
- display_info_path=os.path.join(timestamp_dir, "display.json"),
778
- agent_log_path=os.path.join(timestamp_dir, "agent_log.json")
779
- )
780
- )
781
956
  asyncio.run(serve())
782
957
 
783
958
  if __name__ == '__main__':