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/__init__.py +1 -1
- gui_agents/agents/agent_s.py +73 -25
- gui_agents/agents/global_state.py +13 -1
- gui_agents/agents/grounding.py +19 -4
- gui_agents/agents/manager.py +13 -1
- gui_agents/agents/worker.py +20 -4
- gui_agents/cli_app.py +242 -217
- gui_agents/grpc_app.py +265 -90
- gui_agents/service/agent_service.py +51 -34
- gui_agents/store/registry.py +114 -6
- {lybic_guiagents-0.3.0.dist-info → lybic_guiagents-0.5.0.dist-info}/METADATA +6 -6
- {lybic_guiagents-0.3.0.dist-info → lybic_guiagents-0.5.0.dist-info}/RECORD +16 -16
- {lybic_guiagents-0.3.0.dist-info → lybic_guiagents-0.5.0.dist-info}/WHEEL +0 -0
- {lybic_guiagents-0.3.0.dist-info → lybic_guiagents-0.5.0.dist-info}/entry_points.txt +0 -0
- {lybic_guiagents-0.3.0.dist-info → lybic_guiagents-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {lybic_guiagents-0.3.0.dist-info → lybic_guiagents-0.5.0.dist-info}/top_level.txt +0 -0
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.
|
|
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
|
-
#
|
|
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
|
|
148
|
-
mode: InstanceMode | None = backend_kwargs
|
|
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
|
-
|
|
155
|
-
final_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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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["
|
|
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
|
-
|
|
772
|
+
def _new_lybic_client(self, lybic_auth: LybicAuth) -> LybicClient:
|
|
619
773
|
"""
|
|
620
|
-
Create and
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
|
717
|
-
raise
|
|
910
|
+
if not lybic_auth:
|
|
911
|
+
raise ValueError("Lybic client not initialized. Please call SetGlobalCommonConfig before")
|
|
718
912
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
return
|
|
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
|
-
|
|
739
|
-
servicer = AgentServicer(max_concurrent_task_num=
|
|
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__':
|