gl-computer-use-binary 0.0.1__cp313-cp313-win_amd64.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.
- gl_computer_use/__init__.pyi +7 -0
- gl_computer_use/_setup.pyi +7 -0
- gl_computer_use/_urls.pyi +16 -0
- gl_computer_use/agent/__init__.pyi +0 -0
- gl_computer_use/agent/_anthropic_usage.pyi +20 -0
- gl_computer_use/agent/_openai_usage.pyi +9 -0
- gl_computer_use/agent/_takeover.pyi +37 -0
- gl_computer_use/agent/agent_s.pyi +42 -0
- gl_computer_use/agent/base.pyi +31 -0
- gl_computer_use/agent/openai_agent.pyi +34 -0
- gl_computer_use/agent/runner.pyi +158 -0
- gl_computer_use/artifact/__init__.pyi +0 -0
- gl_computer_use/artifact/base.pyi +83 -0
- gl_computer_use/artifact/local.pyi +79 -0
- gl_computer_use/artifact/minio.pyi +123 -0
- gl_computer_use/client.pyi +265 -0
- gl_computer_use/config.pyi +191 -0
- gl_computer_use/errors.pyi +67 -0
- gl_computer_use/observability/__init__.pyi +3 -0
- gl_computer_use/observability/logging.pyi +24 -0
- gl_computer_use/observability/metrics.pyi +23 -0
- gl_computer_use/observability/retries.pyi +21 -0
- gl_computer_use/observability/tracing.pyi +23 -0
- gl_computer_use/recording.pyi +81 -0
- gl_computer_use/registry.pyi +109 -0
- gl_computer_use/sandbox/__init__.pyi +0 -0
- gl_computer_use/sandbox/base.pyi +294 -0
- gl_computer_use/sandbox/e2b.pyi +238 -0
- gl_computer_use/sandbox/opensandbox.pyi +241 -0
- gl_computer_use/session/__init__.pyi +0 -0
- gl_computer_use/session/accessor.pyi +252 -0
- gl_computer_use/session/manager.pyi +110 -0
- gl_computer_use/session/session.pyi +74 -0
- gl_computer_use/session/state.pyi +19 -0
- gl_computer_use/stream/__init__.pyi +0 -0
- gl_computer_use/stream/client.pyi +82 -0
- gl_computer_use/types.pyi +397 -0
- gl_computer_use.cp313-win_amd64.pyd +0 -0
- gl_computer_use.pyi +90 -0
- gl_computer_use_binary-0.0.1.dist-info/METADATA +424 -0
- gl_computer_use_binary-0.0.1.dist-info/RECORD +44 -0
- gl_computer_use_binary-0.0.1.dist-info/WHEEL +5 -0
- gl_computer_use_binary-0.0.1.dist-info/entry_points.txt +2 -0
- gl_computer_use_binary-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from gl_computer_use.client import GLComputerUseClient as GLComputerUseClient
|
|
2
|
+
from gl_computer_use.config import GLComputerUseConfig as GLComputerUseConfig
|
|
3
|
+
from gl_computer_use.errors import AgentError as AgentError, ArtifactStoreError as ArtifactStoreError, ConfigError as ConfigError, DesktopError as DesktopError, GLComputerUseError as GLComputerUseError, GLConnectionError as GLConnectionError, GLTimeoutError as GLTimeoutError, ProviderMismatchError as ProviderMismatchError, ResumeError as ResumeError, SandboxError as SandboxError, SandboxProvisionError as SandboxProvisionError, SessionBusyError as SessionBusyError, SessionError as SessionError, SessionNotReadyError as SessionNotReadyError, SnapshotError as SnapshotError, TakeoverError as TakeoverError, TakeoverInProgressError as TakeoverInProgressError, TakeoverNotActiveError as TakeoverNotActiveError, TakeoverRequiredError as TakeoverRequiredError, TaskCancelledError as TaskCancelledError, TaskError as TaskError, TaskFailedError as TaskFailedError
|
|
4
|
+
from gl_computer_use.registry import register_agent as register_agent, register_artifact as register_artifact, register_sandbox as register_sandbox
|
|
5
|
+
from gl_computer_use.types import ActionDetail as ActionDetail, EventType as EventType, File as File, ResumeToken as ResumeToken, RunContext as RunContext, SandboxDisposition as SandboxDisposition, SessionState as SessionState, TakeoverContext as TakeoverContext, TakeoverResponse as TakeoverResponse, TakeoverState as TakeoverState, Task as Task, TaskEvent as TaskEvent, TaskResult as TaskResult
|
|
6
|
+
|
|
7
|
+
__all__ = ['GLComputerUseClient', 'GLComputerUseConfig', 'TaskResult', 'TaskEvent', 'ActionDetail', 'EventType', 'RunContext', 'SessionState', 'File', 'Task', 'TakeoverContext', 'TakeoverResponse', 'TakeoverState', 'ResumeToken', 'SandboxDisposition', 'register_agent', 'register_sandbox', 'register_artifact', 'GLComputerUseError', 'ConfigError', 'SandboxError', 'SandboxProvisionError', 'SnapshotError', 'ResumeError', 'ProviderMismatchError', 'AgentError', 'DesktopError', 'TaskError', 'TaskFailedError', 'TaskCancelledError', 'TakeoverRequiredError', 'TakeoverError', 'TakeoverInProgressError', 'TakeoverNotActiveError', 'SessionError', 'SessionBusyError', 'SessionNotReadyError', 'ArtifactStoreError', 'GLConnectionError', 'GLTimeoutError']
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
def install_browsers() -> None:
|
|
2
|
+
"""Download Playwright Chromium browser binaries.
|
|
3
|
+
|
|
4
|
+
Invokes ``playwright install chromium`` using the current Python
|
|
5
|
+
interpreter's ``playwright`` entry point. Exits with a non-zero code if
|
|
6
|
+
the playwright package is not installed or the download fails.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
def split_scheme(value: str, default_scheme: str = 'http') -> tuple[str, str]:
|
|
2
|
+
"""Split an endpoint into its scheme and bare host.
|
|
3
|
+
|
|
4
|
+
A scheme embedded in ``value`` always wins over ``default_scheme`` — an
|
|
5
|
+
endpoint written as ``https://host`` must never be downgraded by a stale
|
|
6
|
+
protocol setting.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
value: Endpoint string, with or without a scheme (e.g. ``host:8080``
|
|
10
|
+
or ``https://host``).
|
|
11
|
+
default_scheme: Scheme to apply when ``value`` carries none.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
A ``(scheme, host)`` tuple. ``host`` never carries a scheme and never
|
|
15
|
+
has trailing slashes.
|
|
16
|
+
"""
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dataclasses import dataclass, field as field
|
|
2
|
+
|
|
3
|
+
@dataclass
|
|
4
|
+
class _Accum:
|
|
5
|
+
tokens_in: int = ...
|
|
6
|
+
tokens_out: int = ...
|
|
7
|
+
cache_creation: int = ...
|
|
8
|
+
cache_read: int = ...
|
|
9
|
+
cost: float = ...
|
|
10
|
+
cost_known: bool = ...
|
|
11
|
+
|
|
12
|
+
def install() -> None:
|
|
13
|
+
"""Monkey-patch gui_agents.s3.core.mllm to use the tracking engine.
|
|
14
|
+
|
|
15
|
+
Must patch the *mllm* namespace because ``mllm.py`` imports
|
|
16
|
+
``LMMEngineAnthropic`` by name; patching the ``engine`` module attribute
|
|
17
|
+
is too late once ``mllm`` has been imported.
|
|
18
|
+
|
|
19
|
+
Idempotent — safe to call multiple times.
|
|
20
|
+
"""
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
def install() -> None:
|
|
2
|
+
"""Monkey-patch gui_agents.s3.core.mllm to use the tracking OpenAI engine.
|
|
3
|
+
|
|
4
|
+
Must patch the *mllm* namespace because ``mllm.py`` imports
|
|
5
|
+
``LMMEngineOpenAI`` by name; patching the ``engine`` module attribute is
|
|
6
|
+
too late once ``mllm`` has been imported.
|
|
7
|
+
|
|
8
|
+
Idempotent — safe to call multiple times.
|
|
9
|
+
"""
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from gl_computer_use.types import RunContext as RunContext
|
|
3
|
+
|
|
4
|
+
async def handle_takeover(ctx: RunContext, reason: str) -> None:
|
|
5
|
+
"""Emit TAKEOVER_STARTED, block until resumed, then emit TAKEOVER_RESUMED.
|
|
6
|
+
|
|
7
|
+
Shared across agent implementations so they call this with identical
|
|
8
|
+
semantics. After this coroutine returns, noop_count is reset to 0 and
|
|
9
|
+
ctx.paused_event is cleared.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
ctx: The current run context (resume_event will be cleared/awaited).
|
|
13
|
+
reason: One of AGENT_REQUESTED, POLICY_TRIGGERED, CALLER_INITIATED.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
class TakeoverPolicy:
|
|
17
|
+
"""Tracks takeover-policy counters for one agent run.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
steps_in_epoch: Real-decision steps since last max_steps takeover.
|
|
21
|
+
stuck_re: Compiled pattern for agent_stuck_patterns, or None.
|
|
22
|
+
"""
|
|
23
|
+
steps_in_epoch: int
|
|
24
|
+
stuck_re: re.Pattern[str] | None
|
|
25
|
+
def __init__(self, stuck_patterns: list[str]) -> None:
|
|
26
|
+
"""Initialise with the compiled stuck-detection regex.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
stuck_patterns: List of lowercase substrings from
|
|
30
|
+
GLComputerUseConfig.stuck_patterns().
|
|
31
|
+
"""
|
|
32
|
+
def is_stuck(self, text: str) -> bool:
|
|
33
|
+
"""Return True if *text* matches any stuck-detection pattern.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
text: Combined reasoning + message text from the current step.
|
|
37
|
+
"""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from gl_computer_use.agent._takeover import TakeoverPolicy as TakeoverPolicy, handle_takeover as handle_takeover
|
|
3
|
+
from gl_computer_use.agent.base import BaseAgent as BaseAgent
|
|
4
|
+
from gl_computer_use.config import GLComputerUseConfig as GLComputerUseConfig
|
|
5
|
+
from gl_computer_use.errors import AgentError as AgentError
|
|
6
|
+
from gl_computer_use.observability.logging import get_logger as get_logger
|
|
7
|
+
from gl_computer_use.observability.tracing import start_span as start_span
|
|
8
|
+
from gl_computer_use.types import ActionDetail as ActionDetail, RunContext as RunContext, TaskEvent as TaskEvent, TaskResult as TaskResult
|
|
9
|
+
|
|
10
|
+
logger: Incomplete
|
|
11
|
+
|
|
12
|
+
class AgentSAgent(BaseAgent):
|
|
13
|
+
"""BaseAgent implementation wrapping simular-ai/Agent-S.
|
|
14
|
+
|
|
15
|
+
Drives the external [screenshot → predict → execute] loop and translates
|
|
16
|
+
each iteration into typed TaskEvent objects. Supports full Phase-2
|
|
17
|
+
takeover semantics via the shared _takeover module.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
_config: SDK configuration including agents_* tuning parameters.
|
|
21
|
+
_policy: Takeover policy tracker for the current run.
|
|
22
|
+
"""
|
|
23
|
+
def __init__(self, config: GLComputerUseConfig) -> None:
|
|
24
|
+
"""Initialise AgentSAgent, verifying the gui-agents import early.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
config: SDK configuration with agents_* and model fields.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
ImportError: When gui-agents is not installed.
|
|
31
|
+
"""
|
|
32
|
+
async def run(self, ctx: RunContext) -> None:
|
|
33
|
+
"""Drive the Agent-S loop and emit events onto ctx.event_queue.
|
|
34
|
+
|
|
35
|
+
Emits TASK_STARTED, one STEP_COMPLETED per real action (plus SCREENSHOT
|
|
36
|
+
events for noop frames), optional TAKEOVER_STARTED / TAKEOVER_RESUMED
|
|
37
|
+
pairs, and a terminal event.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
ctx: Per-task runtime bundle with sandbox, event queue, model
|
|
41
|
+
identity, and cancellation/pause controls.
|
|
42
|
+
"""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from gl_computer_use.types import RunContext as RunContext
|
|
3
|
+
|
|
4
|
+
class BaseAgent(ABC):
|
|
5
|
+
"""Abstract base class for computer-use agent providers.
|
|
6
|
+
|
|
7
|
+
Concrete implementations wrap a specific agent framework (Agent-S, or a
|
|
8
|
+
custom registered agent) and drive the observe-plan-act loop, emitting
|
|
9
|
+
TaskEvent objects onto the queue in RunContext.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
supports_trajectory_persistence: Whether the agent seeds
|
|
13
|
+
``ctx._resume_history`` into its own conversation state on resume
|
|
14
|
+
and writes its running trajectory back to ``ctx._captured_history``
|
|
15
|
+
for the runner to persist on snapshot. False by default — a
|
|
16
|
+
snapshot/resume cycle still preserves the desktop, but the agent
|
|
17
|
+
restarts each task with no memory of the prior conversation.
|
|
18
|
+
Override to True only once both sides are actually wired up.
|
|
19
|
+
"""
|
|
20
|
+
supports_trajectory_persistence: bool
|
|
21
|
+
@abstractmethod
|
|
22
|
+
async def run(self, ctx: RunContext) -> None:
|
|
23
|
+
"""Drive the agent loop and emit step events onto ctx.event_queue.
|
|
24
|
+
|
|
25
|
+
Implementations must emit a terminal event (TASK_COMPLETED,
|
|
26
|
+
TASK_FAILED, or TASK_CANCELLED) as the final item before returning.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
ctx: Per-task runtime bundle including sandbox, event queue,
|
|
30
|
+
model identity, and cancellation/pause controls.
|
|
31
|
+
"""
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from gl_computer_use.agent._takeover import TakeoverPolicy as TakeoverPolicy, handle_takeover as handle_takeover
|
|
3
|
+
from gl_computer_use.agent.base import BaseAgent as BaseAgent
|
|
4
|
+
from gl_computer_use.config import GLComputerUseConfig as GLComputerUseConfig
|
|
5
|
+
from gl_computer_use.errors import AgentError as AgentError
|
|
6
|
+
from gl_computer_use.observability.logging import get_logger as get_logger
|
|
7
|
+
from gl_computer_use.observability.tracing import start_span as start_span
|
|
8
|
+
from gl_computer_use.types import ActionDetail as ActionDetail, RunContext as RunContext, TaskEvent as TaskEvent, TaskResult as TaskResult
|
|
9
|
+
|
|
10
|
+
logger: Incomplete
|
|
11
|
+
|
|
12
|
+
class OpenAIAgent(BaseAgent):
|
|
13
|
+
'''BaseAgent implementation wrapping OpenAI\'s Responses API "computer" tool.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
_config: SDK configuration (openai_api_key, max_steps, stuck patterns).
|
|
17
|
+
'''
|
|
18
|
+
supports_trajectory_persistence: bool
|
|
19
|
+
def __init__(self, config: GLComputerUseConfig) -> None:
|
|
20
|
+
"""Initialise OpenAIAgent, verifying the `openai` package is importable.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
config: SDK configuration with openai_api_key and model fields.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ImportError: When the `openai` package is not installed.
|
|
27
|
+
"""
|
|
28
|
+
async def run(self, ctx: RunContext) -> None:
|
|
29
|
+
"""Drive the OpenAI computer-use loop and emit events onto ctx.event_queue.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
ctx: Per-task runtime bundle with sandbox, event queue, model
|
|
33
|
+
identity, and cancellation/pause controls.
|
|
34
|
+
"""
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from _typeshed import Incomplete
|
|
3
|
+
from gl_computer_use.agent.base import BaseAgent as BaseAgent
|
|
4
|
+
from gl_computer_use.artifact.base import BaseArtifact as BaseArtifact
|
|
5
|
+
from gl_computer_use.config import GLComputerUseConfig as GLComputerUseConfig
|
|
6
|
+
from gl_computer_use.errors import ConfigError as ConfigError, GLComputerUseError as GLComputerUseError, ResumeError as ResumeError, SnapshotError as SnapshotError
|
|
7
|
+
from gl_computer_use.observability.logging import get_logger as get_logger
|
|
8
|
+
from gl_computer_use.observability.metrics import inc_active_sandboxes as inc_active_sandboxes, inc_artifact_upload_failure as inc_artifact_upload_failure, inc_model_drift as inc_model_drift, inc_snapshot_failure as inc_snapshot_failure, inc_snapshot_gc as inc_snapshot_gc, record_artifact_upload as record_artifact_upload, record_provision_seconds as record_provision_seconds, record_resume_seconds as record_resume_seconds, record_snapshot_history_bytes as record_snapshot_history_bytes, record_snapshot_seconds as record_snapshot_seconds
|
|
9
|
+
from gl_computer_use.observability.tracing import start_span as start_span
|
|
10
|
+
from gl_computer_use.recording import PlaywrightRecorder as PlaywrightRecorder, ScreenshotStitcher as ScreenshotStitcher, is_playwright_available as is_playwright_available
|
|
11
|
+
from gl_computer_use.registry import get_registry as get_registry
|
|
12
|
+
from gl_computer_use.sandbox.base import BaseSandbox as BaseSandbox
|
|
13
|
+
from gl_computer_use.types import AgentSession as AgentSession, ResumeToken as ResumeToken, RunContext as RunContext, SandboxDisposition as SandboxDisposition, TaskEvent as TaskEvent
|
|
14
|
+
|
|
15
|
+
logger: Incomplete
|
|
16
|
+
|
|
17
|
+
class AgentRunner:
|
|
18
|
+
"""Orchestrates the agent loop, sandbox lifecycle, and artifact persistence.
|
|
19
|
+
|
|
20
|
+
One AgentRunner is created per Session. Callers interact via prepare(),
|
|
21
|
+
start_agent_task(), on_task_ending(), and cleanup().
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
_config: SDK configuration.
|
|
25
|
+
_artifact_store: Artifact backend for persisting screenshots and files.
|
|
26
|
+
_session_id: Parent session UUID (used for artifact paths).
|
|
27
|
+
_sandbox: Provisioned sandbox instance; set after prepare().
|
|
28
|
+
_agent: Agent provider instance; set after prepare().
|
|
29
|
+
_stream_url: VNC stream URL; set after prepare() if available.
|
|
30
|
+
_recorder: PlaywrightRecorder instance for session recording.
|
|
31
|
+
_screenshot_bytes: Accumulates raw PNG bytes for stitching fallback.
|
|
32
|
+
_screenshot_urls: Accumulates presigned URLs returned by artifact_store.
|
|
33
|
+
_recording_stopped: True once on_task_ending has finished recording.
|
|
34
|
+
"""
|
|
35
|
+
def __init__(self, config: GLComputerUseConfig, artifact_store: BaseArtifact, session_id: str) -> None:
|
|
36
|
+
"""Initialise the AgentRunner with config, artifact store, and session ID.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
config: SDK configuration for this session.
|
|
40
|
+
artifact_store: Backend for persisting screenshots, files, and recordings.
|
|
41
|
+
session_id: Parent session UUID used for artifact paths.
|
|
42
|
+
"""
|
|
43
|
+
def set_disposition(self, disposition: SandboxDisposition | str) -> None:
|
|
44
|
+
"""Set the teardown disposition for this runner instance."""
|
|
45
|
+
async def prepare(self, resume_from: ResumeToken | None = None) -> str | None:
|
|
46
|
+
"""Provision (or restore) the sandbox, build the agent, start recording.
|
|
47
|
+
|
|
48
|
+
When ``resume_from`` is provided the sandbox is restored from the token's
|
|
49
|
+
snapshot instead of provisioned fresh, and the saved agent trajectory is
|
|
50
|
+
loaded so the next run continues the conversation.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
resume_from: A ResumeToken to restore from, or ``None`` for a fresh
|
|
54
|
+
sandbox.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The VNC stream URL if the sandbox exposes one, otherwise None.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
SandboxProvisionError: When the sandbox cannot be allocated.
|
|
61
|
+
ResumeError: When restoring from a snapshot fails.
|
|
62
|
+
"""
|
|
63
|
+
async def cleanup(self) -> None:
|
|
64
|
+
"""Destroy the sandbox and ensure recording is stopped (crash/reset path).
|
|
65
|
+
|
|
66
|
+
This is the disposition-agnostic teardown (always DESTROY). Resumable
|
|
67
|
+
teardown goes through :meth:`finalize`.
|
|
68
|
+
"""
|
|
69
|
+
async def finalize(self, disposition: SandboxDisposition) -> ResumeToken | None:
|
|
70
|
+
"""Disposition-aware teardown: optionally snapshot, then release the box.
|
|
71
|
+
|
|
72
|
+
On ``SNAPSHOT`` (and when the live sandbox has not already been
|
|
73
|
+
snapshotted by :meth:`on_task_ending`), this captures a durable snapshot
|
|
74
|
+
plus the agent trajectory into a :class:`ResumeToken`, GCs the
|
|
75
|
+
predecessor, then destroys the live handle (the snapshot has already
|
|
76
|
+
freed the compute). On ``DESTROY`` it just releases the box.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
disposition: How to dispose of the sandbox.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The successor :class:`ResumeToken` when one was produced, else None.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
SnapshotError: Under ``strict_snapshot=True``, when the capture
|
|
86
|
+
fails. The sandbox is still destroyed before this propagates —
|
|
87
|
+
see the ``finally`` below — so a caller (e.g.
|
|
88
|
+
``Session.__aexit__``, which calls this directly with no
|
|
89
|
+
cleanup fallback of its own on a clean exit) never leaks it.
|
|
90
|
+
"""
|
|
91
|
+
def create_agent_session(self, task_id: str, prompt: str) -> AgentSession:
|
|
92
|
+
"""Build a lean AgentSession identity record for a task.
|
|
93
|
+
|
|
94
|
+
The returned object contains only the immutable, serialisable fields
|
|
95
|
+
that identify the task. Runtime handles live in RunContext, which is
|
|
96
|
+
created separately via create_run_context().
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
task_id: Unique identifier for the task.
|
|
100
|
+
prompt: The user-supplied task description.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
An AgentSession with session_id, task_id, prompt, and model.
|
|
104
|
+
"""
|
|
105
|
+
def create_run_context(self, agent_session: AgentSession, event_queue: asyncio.Queue[TaskEvent], retrieve_files: list[str] | None = None) -> RunContext:
|
|
106
|
+
"""Build the ephemeral RunContext for one task execution.
|
|
107
|
+
|
|
108
|
+
Creates the asyncio control events and binds the live sandbox handle.
|
|
109
|
+
Must be called after prepare() so that self._sandbox is available.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
agent_session: Identity record returned by create_agent_session().
|
|
113
|
+
event_queue: Queue that agent events will be placed onto.
|
|
114
|
+
retrieve_files: Sandbox paths to fetch after task completion.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
A RunContext ready to be passed to BaseAgent.run().
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
GLComputerUseError: When prepare() has not been called yet.
|
|
121
|
+
"""
|
|
122
|
+
def start_agent_task(self, run_ctx: RunContext) -> asyncio.Task[None]:
|
|
123
|
+
"""Start the agent loop as a background asyncio.Task.
|
|
124
|
+
|
|
125
|
+
Used by both the Layer 1 streaming path and the Layer 2 session path.
|
|
126
|
+
The caller drains run_ctx.event_queue independently.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
run_ctx: Pre-built RunContext for this task run.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The asyncio.Task driving the agent loop.
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
GLComputerUseError: When prepare() has not been called yet.
|
|
136
|
+
"""
|
|
137
|
+
async def on_task_ending(self, event: TaskEvent, retrieve_files: list[str] | None = None) -> TaskEvent:
|
|
138
|
+
"""Stop recording, upload artifacts, and patch the terminal TaskResult.
|
|
139
|
+
|
|
140
|
+
Called by StreamClient before the terminal event is yielded to the
|
|
141
|
+
caller. This ensures recording_url/status and screenshot_urls are
|
|
142
|
+
populated in the TaskResult the caller receives.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
event: The terminal TaskEvent (TASK_COMPLETED/FAILED/CANCELLED).
|
|
146
|
+
retrieve_files: Sandbox paths to fetch from the sandbox. Passed
|
|
147
|
+
explicitly so no dynamic attribute is needed on TaskEvent.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
The (potentially mutated) TaskEvent with artifact info attached.
|
|
151
|
+
"""
|
|
152
|
+
@staticmethod
|
|
153
|
+
def new_task_id() -> str:
|
|
154
|
+
"""Generate a new unique task ID.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
A UUID4 string.
|
|
158
|
+
"""
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
class BaseArtifact(ABC):
|
|
4
|
+
"""Abstract base class for artifact storage backends.
|
|
5
|
+
|
|
6
|
+
Concrete implementations write screenshots, files, and recordings to a
|
|
7
|
+
backing store (local disk in Phase 1; S3-compatible object store in Phase 2).
|
|
8
|
+
"""
|
|
9
|
+
supports_history: bool
|
|
10
|
+
@abstractmethod
|
|
11
|
+
async def save_screenshot(self, session_id: str, seq: int, data: bytes) -> str:
|
|
12
|
+
"""Persist a screenshot and return its storage URI.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
session_id: The session that produced this screenshot.
|
|
16
|
+
seq: Zero-based step sequence number used for ordering.
|
|
17
|
+
data: Raw PNG bytes.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
A URI string pointing to the stored artifact.
|
|
21
|
+
"""
|
|
22
|
+
@abstractmethod
|
|
23
|
+
async def save_file(self, session_id: str, path: str, data: bytes) -> str:
|
|
24
|
+
"""Persist an arbitrary file and return its storage URI.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
session_id: The session that produced this file.
|
|
28
|
+
path: Original file path on the sandbox (used to derive the filename).
|
|
29
|
+
data: Raw file bytes.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
A URI string pointing to the stored artifact.
|
|
33
|
+
"""
|
|
34
|
+
@abstractmethod
|
|
35
|
+
async def save_recording(self, session_id: str, data: bytes, ext: str) -> str:
|
|
36
|
+
'''Persist a screen recording and return its storage URI.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
session_id: The session that produced this recording.
|
|
40
|
+
data: Raw recording bytes.
|
|
41
|
+
ext: File extension without leading dot (e.g. ``"mp4"``).
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
A URI string pointing to the stored artifact.
|
|
45
|
+
'''
|
|
46
|
+
async def save_history(self, session_id: str, data: bytes) -> str:
|
|
47
|
+
"""Persist the agent trajectory JSON and return a stable URI.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
session_id: The session that produced this trajectory.
|
|
51
|
+
data: UTF-8 JSON bytes of the conversation trajectory.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A stable URI that :meth:`load_history` and :meth:`delete_history`
|
|
55
|
+
can resolve (e.g. ``file://...`` or ``s3://bucket/key`` — never a
|
|
56
|
+
presigned, expiring URL).
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
NotImplementedError: When the store does not support history.
|
|
60
|
+
"""
|
|
61
|
+
async def load_history(self, uri: str) -> bytes:
|
|
62
|
+
"""Load a previously saved trajectory by its stable URI.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
uri: A URI returned by :meth:`save_history`.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The raw JSON bytes of the trajectory.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
NotImplementedError: When the store does not support history.
|
|
72
|
+
"""
|
|
73
|
+
async def delete_history(self, uri: str) -> None:
|
|
74
|
+
"""Delete a saved trajectory by its stable URI (for rolling GC).
|
|
75
|
+
|
|
76
|
+
A missing/already-deleted trajectory should be treated as success.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
uri: A URI returned by :meth:`save_history`.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
NotImplementedError: When the store does not support history.
|
|
83
|
+
"""
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from gl_computer_use.artifact.base import BaseArtifact as BaseArtifact
|
|
2
|
+
from gl_computer_use.config import GLComputerUseConfig as GLComputerUseConfig
|
|
3
|
+
|
|
4
|
+
class LocalArtifact(BaseArtifact):
|
|
5
|
+
"""Artifact store that persists data to the local filesystem.
|
|
6
|
+
|
|
7
|
+
All write operations use aiofiles for true async I/O without occupying
|
|
8
|
+
thread-pool slots.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
_root: Path to the configured artifact root directory.
|
|
12
|
+
"""
|
|
13
|
+
supports_history: bool
|
|
14
|
+
def __init__(self, config: GLComputerUseConfig) -> None:
|
|
15
|
+
"""Initialise the LocalArtifact store.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
config: SDK configuration; uses config.local_artifact_dir as the root.
|
|
19
|
+
"""
|
|
20
|
+
async def save_screenshot(self, session_id: str, seq: int, data: bytes) -> str:
|
|
21
|
+
"""Write a PNG screenshot to disk and return its file:// URI.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
session_id: The session that produced this screenshot.
|
|
25
|
+
seq: Zero-based step sequence number.
|
|
26
|
+
data: Raw PNG bytes.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
A ``file://`` URI for the stored screenshot.
|
|
30
|
+
"""
|
|
31
|
+
async def save_file(self, session_id: str, path: str, data: bytes) -> str:
|
|
32
|
+
"""Write a sandbox file to disk and return its file:// URI.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
session_id: The session that produced this file.
|
|
36
|
+
path: Original file path on the sandbox; preserved as sanitised path
|
|
37
|
+
segments (leading ``/`` and ``..``/``.`` stripped) so files with
|
|
38
|
+
the same basename from different directories do not collide.
|
|
39
|
+
data: Raw file bytes.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A ``file://`` URI for the stored file.
|
|
43
|
+
"""
|
|
44
|
+
async def save_recording(self, session_id: str, data: bytes, ext: str) -> str:
|
|
45
|
+
'''Write a screen recording to disk and return its file:// URI.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
session_id: The session that produced this recording.
|
|
49
|
+
data: Raw recording bytes.
|
|
50
|
+
ext: File extension without leading dot (e.g. ``"mp4"``).
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A ``file://`` URI for the stored recording.
|
|
54
|
+
'''
|
|
55
|
+
async def save_history(self, session_id: str, data: bytes) -> str:
|
|
56
|
+
"""Write the agent trajectory JSON to disk and return its file:// URI.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
session_id: The session that produced this trajectory.
|
|
60
|
+
data: UTF-8 JSON bytes of the conversation trajectory.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
A ``file://`` URI for the stored trajectory.
|
|
64
|
+
"""
|
|
65
|
+
async def load_history(self, uri: str) -> bytes:
|
|
66
|
+
"""Read a trajectory JSON file given its file:// URI.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
uri: A ``file://`` URI from :meth:`save_history`.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
The raw JSON bytes.
|
|
73
|
+
"""
|
|
74
|
+
async def delete_history(self, uri: str) -> None:
|
|
75
|
+
"""Delete a trajectory file; a missing file is treated as success.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
uri: A ``file://`` URI from :meth:`save_history`.
|
|
79
|
+
"""
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from gl_computer_use._urls import split_scheme as split_scheme
|
|
3
|
+
from gl_computer_use.artifact.base import BaseArtifact as BaseArtifact
|
|
4
|
+
from gl_computer_use.config import GLComputerUseConfig as GLComputerUseConfig
|
|
5
|
+
from gl_computer_use.errors import ArtifactStoreError as ArtifactStoreError
|
|
6
|
+
from gl_computer_use.observability.logging import get_logger as get_logger
|
|
7
|
+
from gl_computer_use.observability.retries import retry_artifact as retry_artifact
|
|
8
|
+
from gl_computer_use.observability.tracing import start_span as start_span
|
|
9
|
+
|
|
10
|
+
logger: Incomplete
|
|
11
|
+
|
|
12
|
+
class MinIOArtifact(BaseArtifact):
|
|
13
|
+
"""BaseArtifact backed by a MinIO (S3-compatible) object store.
|
|
14
|
+
|
|
15
|
+
All SDK calls use aiobotocore for true async I/O — no thread pool involved.
|
|
16
|
+
Each save method returns a presigned URL valid for
|
|
17
|
+
``config.presigned_url_expiry_seconds``.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
_endpoint_url: MinIO server endpoint URL (with scheme), or ``None`` for
|
|
21
|
+
the AWS S3 global endpoint so botocore resolves the regional endpoint
|
|
22
|
+
from ``_region``.
|
|
23
|
+
_access_key: MinIO access key.
|
|
24
|
+
_secret_key: MinIO secret key.
|
|
25
|
+
_bucket: Target bucket name.
|
|
26
|
+
_region: AWS region of the bucket (None for region-agnostic MinIO).
|
|
27
|
+
_prefix: Optional key prefix within the bucket (empty = bucket root).
|
|
28
|
+
_expiry: Presigned URL TTL in seconds.
|
|
29
|
+
_session: aiobotocore session for creating async S3 clients.
|
|
30
|
+
_bucket_ensured: Flag so bucket creation only runs once.
|
|
31
|
+
_bucket_lock: asyncio.Lock guarding the once-only bucket init.
|
|
32
|
+
"""
|
|
33
|
+
supports_history: bool
|
|
34
|
+
def __init__(self, config: GLComputerUseConfig) -> None:
|
|
35
|
+
"""Initialise MinIOArtifact from the SDK configuration.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
config: SDK configuration with object_store_* fields populated.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ImportError: When the aiobotocore package is not installed.
|
|
42
|
+
ArtifactStoreError: When the endpoint is absent.
|
|
43
|
+
"""
|
|
44
|
+
async def save_screenshot(self, session_id: str, seq: int, data: bytes) -> str:
|
|
45
|
+
"""Upload a screenshot PNG and return a presigned URL.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
session_id: Parent session UUID (used for object key prefix).
|
|
49
|
+
seq: Zero-based step sequence number.
|
|
50
|
+
data: Raw PNG bytes.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Presigned GET URL valid for ``presigned_url_expiry_seconds``.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ArtifactStoreError: On upload failure.
|
|
57
|
+
"""
|
|
58
|
+
async def save_file(self, session_id: str, path: str, data: bytes) -> str:
|
|
59
|
+
"""Upload a retrieved sandbox file and return a presigned URL.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
session_id: Parent session UUID.
|
|
63
|
+
path: Original sandbox path; preserved as sanitised key segments
|
|
64
|
+
(leading ``/`` and ``..``/``.`` segments stripped) so files with
|
|
65
|
+
the same basename from different directories do not collide.
|
|
66
|
+
data: Raw file bytes.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Presigned GET URL valid for ``presigned_url_expiry_seconds``.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ArtifactStoreError: On upload failure.
|
|
73
|
+
"""
|
|
74
|
+
async def save_recording(self, session_id: str, data: bytes, ext: str) -> str:
|
|
75
|
+
'''Upload a session recording and return a presigned URL.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
session_id: Parent session UUID.
|
|
79
|
+
data: Raw recording bytes (WebM or GIF).
|
|
80
|
+
ext: File extension without leading dot (e.g. ``"webm"`` or ``"gif"``).
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Presigned GET URL valid for ``presigned_url_expiry_seconds``.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ArtifactStoreError: On upload failure.
|
|
87
|
+
'''
|
|
88
|
+
async def save_history(self, session_id: str, data: bytes) -> str:
|
|
89
|
+
"""Upload the agent trajectory JSON and return a stable s3:// URI.
|
|
90
|
+
|
|
91
|
+
Unlike the screenshot/recording helpers, this returns a stable
|
|
92
|
+
``s3://bucket/key`` reference (not a presigned, expiring URL) so the URI
|
|
93
|
+
can be stored in a long-lived :class:`ResumeToken` and later resolved by
|
|
94
|
+
:meth:`load_history` / :meth:`delete_history`.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
session_id: The session that produced this trajectory.
|
|
98
|
+
data: UTF-8 JSON bytes of the conversation trajectory.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
A stable ``s3://bucket/key`` URI.
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
ArtifactStoreError: On upload failure.
|
|
105
|
+
"""
|
|
106
|
+
async def load_history(self, uri: str) -> bytes:
|
|
107
|
+
"""Download a trajectory JSON given its s3:// URI.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
uri: An ``s3://bucket/key`` URI from :meth:`save_history`.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
The raw JSON bytes.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
ArtifactStoreError: On download failure.
|
|
117
|
+
"""
|
|
118
|
+
async def delete_history(self, uri: str) -> None:
|
|
119
|
+
"""Delete a trajectory object; failures are logged, not raised.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
uri: An ``s3://bucket/key`` URI from :meth:`save_history`.
|
|
123
|
+
"""
|