strix-agent 0.1.1__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.
Files changed (99) hide show
  1. strix/__init__.py +0 -0
  2. strix/agents/StrixAgent/__init__.py +4 -0
  3. strix/agents/StrixAgent/strix_agent.py +60 -0
  4. strix/agents/StrixAgent/system_prompt.jinja +504 -0
  5. strix/agents/__init__.py +10 -0
  6. strix/agents/base_agent.py +394 -0
  7. strix/agents/state.py +139 -0
  8. strix/cli/__init__.py +4 -0
  9. strix/cli/app.py +1124 -0
  10. strix/cli/assets/cli.tcss +680 -0
  11. strix/cli/main.py +542 -0
  12. strix/cli/tool_components/__init__.py +39 -0
  13. strix/cli/tool_components/agents_graph_renderer.py +129 -0
  14. strix/cli/tool_components/base_renderer.py +61 -0
  15. strix/cli/tool_components/browser_renderer.py +107 -0
  16. strix/cli/tool_components/file_edit_renderer.py +95 -0
  17. strix/cli/tool_components/finish_renderer.py +32 -0
  18. strix/cli/tool_components/notes_renderer.py +108 -0
  19. strix/cli/tool_components/proxy_renderer.py +255 -0
  20. strix/cli/tool_components/python_renderer.py +34 -0
  21. strix/cli/tool_components/registry.py +72 -0
  22. strix/cli/tool_components/reporting_renderer.py +53 -0
  23. strix/cli/tool_components/scan_info_renderer.py +58 -0
  24. strix/cli/tool_components/terminal_renderer.py +99 -0
  25. strix/cli/tool_components/thinking_renderer.py +29 -0
  26. strix/cli/tool_components/user_message_renderer.py +43 -0
  27. strix/cli/tool_components/web_search_renderer.py +28 -0
  28. strix/cli/tracer.py +308 -0
  29. strix/llm/__init__.py +14 -0
  30. strix/llm/config.py +19 -0
  31. strix/llm/llm.py +310 -0
  32. strix/llm/memory_compressor.py +206 -0
  33. strix/llm/request_queue.py +63 -0
  34. strix/llm/utils.py +84 -0
  35. strix/prompts/__init__.py +113 -0
  36. strix/prompts/coordination/root_agent.jinja +41 -0
  37. strix/prompts/vulnerabilities/authentication_jwt.jinja +129 -0
  38. strix/prompts/vulnerabilities/business_logic.jinja +143 -0
  39. strix/prompts/vulnerabilities/csrf.jinja +168 -0
  40. strix/prompts/vulnerabilities/idor.jinja +164 -0
  41. strix/prompts/vulnerabilities/race_conditions.jinja +194 -0
  42. strix/prompts/vulnerabilities/rce.jinja +222 -0
  43. strix/prompts/vulnerabilities/sql_injection.jinja +216 -0
  44. strix/prompts/vulnerabilities/ssrf.jinja +168 -0
  45. strix/prompts/vulnerabilities/xss.jinja +221 -0
  46. strix/prompts/vulnerabilities/xxe.jinja +276 -0
  47. strix/runtime/__init__.py +19 -0
  48. strix/runtime/docker_runtime.py +298 -0
  49. strix/runtime/runtime.py +25 -0
  50. strix/runtime/tool_server.py +97 -0
  51. strix/tools/__init__.py +64 -0
  52. strix/tools/agents_graph/__init__.py +16 -0
  53. strix/tools/agents_graph/agents_graph_actions.py +610 -0
  54. strix/tools/agents_graph/agents_graph_actions_schema.xml +223 -0
  55. strix/tools/argument_parser.py +120 -0
  56. strix/tools/browser/__init__.py +4 -0
  57. strix/tools/browser/browser_actions.py +236 -0
  58. strix/tools/browser/browser_actions_schema.xml +183 -0
  59. strix/tools/browser/browser_instance.py +533 -0
  60. strix/tools/browser/tab_manager.py +342 -0
  61. strix/tools/executor.py +302 -0
  62. strix/tools/file_edit/__init__.py +4 -0
  63. strix/tools/file_edit/file_edit_actions.py +141 -0
  64. strix/tools/file_edit/file_edit_actions_schema.xml +128 -0
  65. strix/tools/finish/__init__.py +4 -0
  66. strix/tools/finish/finish_actions.py +167 -0
  67. strix/tools/finish/finish_actions_schema.xml +45 -0
  68. strix/tools/notes/__init__.py +14 -0
  69. strix/tools/notes/notes_actions.py +191 -0
  70. strix/tools/notes/notes_actions_schema.xml +150 -0
  71. strix/tools/proxy/__init__.py +20 -0
  72. strix/tools/proxy/proxy_actions.py +101 -0
  73. strix/tools/proxy/proxy_actions_schema.xml +267 -0
  74. strix/tools/proxy/proxy_manager.py +785 -0
  75. strix/tools/python/__init__.py +4 -0
  76. strix/tools/python/python_actions.py +47 -0
  77. strix/tools/python/python_actions_schema.xml +131 -0
  78. strix/tools/python/python_instance.py +172 -0
  79. strix/tools/python/python_manager.py +131 -0
  80. strix/tools/registry.py +196 -0
  81. strix/tools/reporting/__init__.py +6 -0
  82. strix/tools/reporting/reporting_actions.py +63 -0
  83. strix/tools/reporting/reporting_actions_schema.xml +30 -0
  84. strix/tools/terminal/__init__.py +4 -0
  85. strix/tools/terminal/terminal_actions.py +53 -0
  86. strix/tools/terminal/terminal_actions_schema.xml +114 -0
  87. strix/tools/terminal/terminal_instance.py +231 -0
  88. strix/tools/terminal/terminal_manager.py +191 -0
  89. strix/tools/thinking/__init__.py +4 -0
  90. strix/tools/thinking/thinking_actions.py +18 -0
  91. strix/tools/thinking/thinking_actions_schema.xml +52 -0
  92. strix/tools/web_search/__init__.py +4 -0
  93. strix/tools/web_search/web_search_actions.py +80 -0
  94. strix/tools/web_search/web_search_actions_schema.xml +83 -0
  95. strix_agent-0.1.1.dist-info/LICENSE +201 -0
  96. strix_agent-0.1.1.dist-info/METADATA +200 -0
  97. strix_agent-0.1.1.dist-info/RECORD +99 -0
  98. strix_agent-0.1.1.dist-info/WHEEL +4 -0
  99. strix_agent-0.1.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,298 @@
1
+ import logging
2
+ import os
3
+ import secrets
4
+ import socket
5
+ import time
6
+ from pathlib import Path
7
+ from typing import cast
8
+
9
+ import docker
10
+ from docker.errors import DockerException, NotFound
11
+ from docker.models.containers import Container
12
+
13
+ from .runtime import AbstractRuntime, SandboxInfo
14
+
15
+
16
+ STRIX_AGENT_LABEL = "StrixAgent_ID"
17
+ STRIX_SCAN_LABEL = "StrixScan_ID"
18
+ STRIX_IMAGE = os.getenv("STRIX_IMAGE", "ghcr.io/usestrix/strix-sandbox:0.1.1")
19
+ logger = logging.getLogger(__name__)
20
+
21
+ _initialized_volumes: set[str] = set()
22
+
23
+
24
+ class DockerRuntime(AbstractRuntime):
25
+ def __init__(self) -> None:
26
+ try:
27
+ self.client = docker.from_env()
28
+ except DockerException as e:
29
+ logger.exception("Failed to connect to Docker daemon")
30
+ raise RuntimeError("Docker is not available or not configured correctly.") from e
31
+
32
+ def _get_docker_socket_path(self) -> str | None:
33
+ standard_socket = "/var/run/docker.sock"
34
+ if Path(standard_socket).exists():
35
+ return standard_socket
36
+
37
+ if os.name == "nt":
38
+ windows_socket = "//./pipe/docker_engine"
39
+ if Path(windows_socket).exists():
40
+ return windows_socket
41
+
42
+ docker_host = os.environ.get("DOCKER_HOST", "")
43
+ if docker_host.startswith("unix://"):
44
+ socket_path = docker_host.replace("unix://", "")
45
+ if Path(socket_path).exists():
46
+ return socket_path
47
+
48
+ return None
49
+
50
+ def _generate_sandbox_token(self) -> str:
51
+ return secrets.token_urlsafe(32)
52
+
53
+ def _get_scan_id(self, agent_id: str) -> str:
54
+ try:
55
+ from strix.cli.tracer import get_global_tracer
56
+
57
+ tracer = get_global_tracer()
58
+ if tracer and tracer.scan_config:
59
+ return str(tracer.scan_config.get("scan_id", "default-scan"))
60
+ except ImportError:
61
+ logger.debug("Failed to import tracer, using fallback scan ID")
62
+ except AttributeError:
63
+ logger.debug("Tracer missing scan_config, using fallback scan ID")
64
+
65
+ return f"scan-{agent_id.split('-')[0]}"
66
+
67
+ def _find_available_port(self) -> int:
68
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
69
+ s.bind(("", 0))
70
+ return cast("int", s.getsockname()[1])
71
+
72
+ def _get_workspace_volume_name(self, scan_id: str) -> str:
73
+ return f"strix-workspace-{scan_id}"
74
+
75
+ def _get_sandbox_by_agent_id(self, agent_id: str) -> Container | None:
76
+ try:
77
+ containers = self.client.containers.list(
78
+ filters={"label": f"{STRIX_AGENT_LABEL}={agent_id}"}
79
+ )
80
+ if not containers:
81
+ return None
82
+ if len(containers) > 1:
83
+ logger.warning(
84
+ "Multiple sandboxes found for agent ID %s, using the first one.", agent_id
85
+ )
86
+ return cast("Container", containers[0])
87
+ except DockerException as e:
88
+ logger.warning("Failed to get sandbox by agent ID %s: %s", agent_id, e)
89
+ return None
90
+
91
+ def _ensure_workspace_volume(self, volume_name: str) -> None:
92
+ try:
93
+ self.client.volumes.get(volume_name)
94
+ logger.info(f"Using existing workspace volume: {volume_name}")
95
+ except NotFound:
96
+ self.client.volumes.create(name=volume_name, driver="local")
97
+ logger.info(f"Created new workspace volume: {volume_name}")
98
+
99
+ def _copy_local_directory_to_container(self, container: Container, local_path: str) -> None:
100
+ import tarfile
101
+ from io import BytesIO
102
+
103
+ try:
104
+ local_path_obj = Path(local_path).resolve()
105
+ if not local_path_obj.exists() or not local_path_obj.is_dir():
106
+ logger.warning(f"Local path does not exist or is not a directory: {local_path_obj}")
107
+ return
108
+
109
+ logger.info(f"Copying local directory {local_path_obj} to container {container.id}")
110
+
111
+ tar_buffer = BytesIO()
112
+ with tarfile.open(fileobj=tar_buffer, mode="w") as tar:
113
+ for item in local_path_obj.rglob("*"):
114
+ if item.is_file():
115
+ arcname = item.relative_to(local_path_obj)
116
+ tar.add(item, arcname=arcname)
117
+
118
+ tar_buffer.seek(0)
119
+
120
+ container.put_archive("/shared_workspace", tar_buffer.getvalue())
121
+
122
+ container.exec_run(
123
+ "chown -R pentester:pentester /shared_workspace && chmod -R 755 /shared_workspace",
124
+ user="root",
125
+ )
126
+
127
+ logger.info(
128
+ f"Successfully copied {local_path_obj} to /shared_workspace in container "
129
+ f"{container.id}"
130
+ )
131
+
132
+ except (OSError, DockerException):
133
+ logger.exception("Failed to copy local directory to container")
134
+
135
+ async def create_sandbox(
136
+ self, agent_id: str, existing_token: str | None = None, local_source_path: str | None = None
137
+ ) -> SandboxInfo:
138
+ sandbox = self._get_sandbox_by_agent_id(agent_id)
139
+ auth_token = existing_token or self._generate_sandbox_token()
140
+
141
+ scan_id = self._get_scan_id(agent_id)
142
+ volume_name = self._get_workspace_volume_name(scan_id)
143
+
144
+ self._ensure_workspace_volume(volume_name)
145
+
146
+ if not sandbox:
147
+ logger.info("Creating new Docker sandbox for agent %s", agent_id)
148
+ try:
149
+ tool_server_port = self._find_available_port()
150
+ caido_port = self._find_available_port()
151
+
152
+ volumes_config = {volume_name: {"bind": "/shared_workspace", "mode": "rw"}}
153
+
154
+ docker_socket_path = self._get_docker_socket_path()
155
+ if docker_socket_path and Path(docker_socket_path).exists():
156
+ volumes_config[docker_socket_path] = {
157
+ "bind": "/var/run/docker.sock",
158
+ "mode": "rw",
159
+ }
160
+ logger.info(f"Mounting Docker socket from {docker_socket_path}")
161
+ else:
162
+ logger.warning("Docker socket not found or not accessible")
163
+
164
+ sandbox = self.client.containers.run(
165
+ STRIX_IMAGE,
166
+ command="sleep infinity",
167
+ detach=True,
168
+ network_mode="host",
169
+ cap_add=["NET_ADMIN", "NET_RAW"],
170
+ labels={
171
+ STRIX_AGENT_LABEL: agent_id,
172
+ STRIX_SCAN_LABEL: scan_id,
173
+ },
174
+ environment={
175
+ "PYTHONUNBUFFERED": "1",
176
+ "STRIX_AGENT_ID": agent_id,
177
+ "STRIX_SANDBOX_TOKEN": auth_token,
178
+ "STRIX_TOOL_SERVER_PORT": str(tool_server_port),
179
+ "CAIDO_PORT": str(caido_port),
180
+ **(
181
+ {"DOCKER_HOST": os.environ["DOCKER_HOST"]}
182
+ if "DOCKER_HOST" in os.environ
183
+ else {}
184
+ ),
185
+ },
186
+ volumes=volumes_config,
187
+ tty=True,
188
+ )
189
+ logger.info(
190
+ "Created new sandbox %s for agent %s with shared workspace %s",
191
+ sandbox.id,
192
+ agent_id,
193
+ volume_name,
194
+ )
195
+ except DockerException as e:
196
+ raise RuntimeError(f"Failed to create Docker sandbox: {e}") from e
197
+
198
+ assert sandbox is not None
199
+ if sandbox.status != "running":
200
+ sandbox.start()
201
+ time.sleep(15)
202
+
203
+ if local_source_path and volume_name not in _initialized_volumes:
204
+ self._copy_local_directory_to_container(sandbox, local_source_path)
205
+ _initialized_volumes.add(volume_name)
206
+
207
+ sandbox_id = sandbox.id
208
+ if sandbox_id is None:
209
+ raise RuntimeError("Docker container ID is unexpectedly None")
210
+
211
+ tool_server_port_str = sandbox.attrs["Config"]["Env"][
212
+ next(
213
+ (
214
+ i
215
+ for i, s in enumerate(sandbox.attrs["Config"]["Env"])
216
+ if s.startswith("STRIX_TOOL_SERVER_PORT=")
217
+ ),
218
+ -1,
219
+ )
220
+ ].split("=")[1]
221
+ tool_server_port = int(tool_server_port_str)
222
+
223
+ api_url = await self.get_sandbox_url(sandbox_id, tool_server_port)
224
+
225
+ return {
226
+ "workspace_id": sandbox_id,
227
+ "api_url": api_url,
228
+ "auth_token": auth_token,
229
+ "tool_server_port": tool_server_port,
230
+ }
231
+
232
+ async def get_sandbox_url(self, sandbox_id: str, port: int) -> str:
233
+ try:
234
+ container = self.client.containers.get(sandbox_id)
235
+ container.reload()
236
+
237
+ host = "localhost"
238
+ if "DOCKER_HOST" in os.environ:
239
+ docker_host = os.environ["DOCKER_HOST"]
240
+ if "://" in docker_host:
241
+ host = docker_host.split("://")[1].split(":")[0]
242
+
243
+ except NotFound:
244
+ raise ValueError(f"Sandbox {sandbox_id} not found.") from None
245
+ except DockerException as e:
246
+ raise RuntimeError(f"Failed to get sandbox URL for {sandbox_id}: {e}") from e
247
+ else:
248
+ return f"http://{host}:{port}"
249
+
250
+ async def destroy_sandbox(self, sandbox_id: str) -> None:
251
+ logger.info("Destroying Docker sandbox %s", sandbox_id)
252
+ try:
253
+ container = self.client.containers.get(sandbox_id)
254
+
255
+ scan_id = None
256
+ if container.labels and STRIX_SCAN_LABEL in container.labels:
257
+ scan_id = container.labels[STRIX_SCAN_LABEL]
258
+
259
+ container.stop()
260
+ container.remove()
261
+ logger.info("Successfully destroyed sandbox %s", sandbox_id)
262
+
263
+ if scan_id:
264
+ await self._cleanup_scan_workspace_if_empty(scan_id)
265
+
266
+ except NotFound:
267
+ logger.warning("Sandbox %s not found for destruction.", sandbox_id)
268
+ except DockerException as e:
269
+ logger.warning("Failed to destroy sandbox %s: %s", sandbox_id, e)
270
+
271
+ async def _cleanup_scan_workspace_if_empty(self, scan_id: str) -> None:
272
+ try:
273
+ volume_name = self._get_workspace_volume_name(scan_id)
274
+
275
+ containers = self.client.containers.list(
276
+ all=True, filters={"label": f"{STRIX_SCAN_LABEL}={scan_id}"}
277
+ )
278
+
279
+ if not containers:
280
+ try:
281
+ volume = self.client.volumes.get(volume_name)
282
+ volume.remove()
283
+ logger.info(
284
+ f"Cleaned up workspace volume {volume_name} for completed scan {scan_id}"
285
+ )
286
+
287
+ _initialized_volumes.discard(volume_name)
288
+
289
+ except NotFound:
290
+ logger.debug(f"Volume {volume_name} already removed")
291
+ except DockerException as e:
292
+ logger.warning(f"Failed to remove volume {volume_name}: {e}")
293
+
294
+ except DockerException as e:
295
+ logger.warning("Error during workspace cleanup for scan %s: %s", scan_id, e)
296
+
297
+ async def cleanup_scan_workspace(self, scan_id: str) -> None:
298
+ await self._cleanup_scan_workspace_if_empty(scan_id)
@@ -0,0 +1,25 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TypedDict
3
+
4
+
5
+ class SandboxInfo(TypedDict):
6
+ workspace_id: str
7
+ api_url: str
8
+ auth_token: str | None
9
+ tool_server_port: int
10
+
11
+
12
+ class AbstractRuntime(ABC):
13
+ @abstractmethod
14
+ async def create_sandbox(
15
+ self, agent_id: str, existing_token: str | None = None, local_source_path: str | None = None
16
+ ) -> SandboxInfo:
17
+ raise NotImplementedError
18
+
19
+ @abstractmethod
20
+ async def get_sandbox_url(self, sandbox_id: str, port: int) -> str:
21
+ raise NotImplementedError
22
+
23
+ @abstractmethod
24
+ async def destroy_sandbox(self, sandbox_id: str) -> None:
25
+ raise NotImplementedError
@@ -0,0 +1,97 @@
1
+ import logging
2
+ import os
3
+ from typing import Any
4
+
5
+ from fastapi import Depends, FastAPI, HTTPException, status
6
+ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
7
+ from pydantic import BaseModel, ValidationError
8
+
9
+
10
+ SANDBOX_MODE = os.getenv("STRIX_SANDBOX_MODE", "false").lower() == "true"
11
+ if not SANDBOX_MODE:
12
+ raise RuntimeError("Tool server should only run in sandbox mode (STRIX_SANDBOX_MODE=true)")
13
+
14
+ EXPECTED_TOKEN = os.getenv("STRIX_SANDBOX_TOKEN")
15
+ if not EXPECTED_TOKEN:
16
+ raise RuntimeError("STRIX_SANDBOX_TOKEN environment variable is required in sandbox mode")
17
+
18
+ app = FastAPI()
19
+ logger = logging.getLogger(__name__)
20
+ security = HTTPBearer()
21
+
22
+ security_dependency = Depends(security)
23
+
24
+
25
+ def verify_token(credentials: HTTPAuthorizationCredentials) -> str:
26
+ if not credentials or credentials.scheme != "Bearer":
27
+ logger.warning("Authentication failed: Invalid or missing Bearer token scheme")
28
+ raise HTTPException(
29
+ status_code=status.HTTP_401_UNAUTHORIZED,
30
+ detail="Invalid authentication scheme. Bearer token required.",
31
+ headers={"WWW-Authenticate": "Bearer"},
32
+ )
33
+
34
+ if credentials.credentials != EXPECTED_TOKEN:
35
+ logger.warning("Authentication failed: Invalid token provided from remote host")
36
+ raise HTTPException(
37
+ status_code=status.HTTP_401_UNAUTHORIZED,
38
+ detail="Invalid authentication token",
39
+ headers={"WWW-Authenticate": "Bearer"},
40
+ )
41
+
42
+ logger.debug("Authentication successful for tool execution request")
43
+ return credentials.credentials
44
+
45
+
46
+ class ToolExecutionRequest(BaseModel):
47
+ tool_name: str
48
+ kwargs: dict[str, Any]
49
+
50
+
51
+ class ToolExecutionResponse(BaseModel):
52
+ result: Any | None = None
53
+ error: str | None = None
54
+
55
+
56
+ @app.post("/execute", response_model=ToolExecutionResponse)
57
+ async def execute_tool(
58
+ request: ToolExecutionRequest, credentials: HTTPAuthorizationCredentials = security_dependency
59
+ ) -> ToolExecutionResponse:
60
+ verify_token(credentials)
61
+
62
+ from strix.tools.argument_parser import ArgumentConversionError, convert_arguments
63
+ from strix.tools.registry import get_tool_by_name
64
+
65
+ try:
66
+ tool_func = get_tool_by_name(request.tool_name)
67
+ if not tool_func:
68
+ return ToolExecutionResponse(error=f"Tool '{request.tool_name}' not found")
69
+
70
+ converted_kwargs = convert_arguments(tool_func, request.kwargs)
71
+
72
+ result = tool_func(**converted_kwargs)
73
+
74
+ return ToolExecutionResponse(result=result)
75
+
76
+ except (ArgumentConversionError, ValidationError) as e:
77
+ logger.warning("Invalid tool arguments: %s", e)
78
+ return ToolExecutionResponse(error=f"Invalid arguments: {e}")
79
+ except TypeError as e:
80
+ logger.warning("Tool execution type error: %s", e)
81
+ return ToolExecutionResponse(error=f"Tool execution error: {e}")
82
+ except ValueError as e:
83
+ logger.warning("Tool execution value error: %s", e)
84
+ return ToolExecutionResponse(error=f"Tool execution error: {e}")
85
+ except Exception:
86
+ logger.exception("Unexpected error during tool execution")
87
+ return ToolExecutionResponse(error="Internal server error")
88
+
89
+
90
+ @app.get("/health")
91
+ async def health_check() -> dict[str, str]:
92
+ return {
93
+ "status": "healthy",
94
+ "sandbox_mode": str(SANDBOX_MODE),
95
+ "environment": "sandbox" if SANDBOX_MODE else "main",
96
+ "auth_configured": "true" if EXPECTED_TOKEN else "false",
97
+ }
@@ -0,0 +1,64 @@
1
+ import os
2
+
3
+ from .executor import (
4
+ execute_tool,
5
+ execute_tool_invocation,
6
+ execute_tool_with_validation,
7
+ extract_screenshot_from_result,
8
+ process_tool_invocations,
9
+ remove_screenshot_from_result,
10
+ validate_tool_availability,
11
+ )
12
+ from .registry import (
13
+ ImplementedInClientSideOnlyError,
14
+ get_tool_by_name,
15
+ get_tool_names,
16
+ get_tools_prompt,
17
+ needs_agent_state,
18
+ register_tool,
19
+ tools,
20
+ )
21
+
22
+
23
+ SANDBOX_MODE = os.getenv("STRIX_SANDBOX_MODE", "false").lower() == "true"
24
+
25
+ HAS_PERPLEXITY_API = bool(os.getenv("PERPLEXITY_API_KEY"))
26
+
27
+ if not SANDBOX_MODE:
28
+ from .agents_graph import * # noqa: F403
29
+ from .browser import * # noqa: F403
30
+ from .file_edit import * # noqa: F403
31
+ from .finish import * # noqa: F403
32
+ from .notes import * # noqa: F403
33
+ from .proxy import * # noqa: F403
34
+ from .python import * # noqa: F403
35
+ from .reporting import * # noqa: F403
36
+ from .terminal import * # noqa: F403
37
+ from .thinking import * # noqa: F403
38
+
39
+ if HAS_PERPLEXITY_API:
40
+ from .web_search import * # noqa: F403
41
+ else:
42
+ from .browser import * # noqa: F403
43
+ from .file_edit import * # noqa: F403
44
+ from .notes import * # noqa: F403
45
+ from .proxy import * # noqa: F403
46
+ from .python import * # noqa: F403
47
+ from .terminal import * # noqa: F403
48
+
49
+ __all__ = [
50
+ "ImplementedInClientSideOnlyError",
51
+ "execute_tool",
52
+ "execute_tool_invocation",
53
+ "execute_tool_with_validation",
54
+ "extract_screenshot_from_result",
55
+ "get_tool_by_name",
56
+ "get_tool_names",
57
+ "get_tools_prompt",
58
+ "needs_agent_state",
59
+ "process_tool_invocations",
60
+ "register_tool",
61
+ "remove_screenshot_from_result",
62
+ "tools",
63
+ "validate_tool_availability",
64
+ ]
@@ -0,0 +1,16 @@
1
+ from .agents_graph_actions import (
2
+ agent_finish,
3
+ create_agent,
4
+ send_message_to_agent,
5
+ view_agent_graph,
6
+ wait_for_message,
7
+ )
8
+
9
+
10
+ __all__ = [
11
+ "agent_finish",
12
+ "create_agent",
13
+ "send_message_to_agent",
14
+ "view_agent_graph",
15
+ "wait_for_message",
16
+ ]