kolega-code 0.1.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.
- kolega_code/__init__.py +151 -0
- kolega_code/agent/__init__.py +42 -0
- kolega_code/agent/baseagent.py +998 -0
- kolega_code/agent/browseragent.py +123 -0
- kolega_code/agent/coder.py +157 -0
- kolega_code/agent/common.py +41 -0
- kolega_code/agent/compression.py +81 -0
- kolega_code/agent/context.py +112 -0
- kolega_code/agent/conversation.py +408 -0
- kolega_code/agent/generalagent.py +146 -0
- kolega_code/agent/investigationagent.py +123 -0
- kolega_code/agent/planningagent.py +187 -0
- kolega_code/agent/prompt_provider.py +196 -0
- kolega_code/agent/prompt_templates/agents/browser.j2 +102 -0
- kolega_code/agent/prompt_templates/agents/coder_cli_mode.j2 +127 -0
- kolega_code/agent/prompt_templates/agents/general.j2 +68 -0
- kolega_code/agent/prompt_templates/agents/investigation.j2 +72 -0
- kolega_code/agent/prompt_templates/common/frontend_guidance.md +36 -0
- kolega_code/agent/prompt_templates/common/kolega_md_instructions.md +14 -0
- kolega_code/agent/prompt_templates/environment_variables/workspace_env_vars.md +11 -0
- kolega_code/agent/prompt_templates/template_guidance/expo-template.md +379 -0
- kolega_code/agent/prompt_templates/template_guidance/html-website-template.md +3 -0
- kolega_code/agent/prompt_templates/template_guidance/mern-stack-template.md +3 -0
- kolega_code/agent/prompt_templates/template_guidance/react-vite-shadcdn-template.md +182 -0
- kolega_code/agent/prompts.py +192 -0
- kolega_code/agent/tests/__init__.py +0 -0
- kolega_code/agent/tests/llm/__init__.py +0 -0
- kolega_code/agent/tests/llm/test_anthropic_token_counting.py +633 -0
- kolega_code/agent/tests/llm/test_billing_openai_cache.py +74 -0
- kolega_code/agent/tests/llm/test_client.py +773 -0
- kolega_code/agent/tests/llm/test_dashscope_mapping.py +32 -0
- kolega_code/agent/tests/llm/test_error_boundary.py +322 -0
- kolega_code/agent/tests/llm/test_exceptions.py +249 -0
- kolega_code/agent/tests/llm/test_instrumented_client.py +536 -0
- kolega_code/agent/tests/llm/test_instrumented_client_integration.py +547 -0
- kolega_code/agent/tests/llm/test_langfuse_normalization.py +39 -0
- kolega_code/agent/tests/llm/test_model_specs.py +17 -0
- kolega_code/agent/tests/llm/test_openai_cached_tokens.py +58 -0
- kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +74 -0
- kolega_code/agent/tests/llm/test_openai_message_conversion.py +30 -0
- kolega_code/agent/tests/llm/test_openai_token_counting.py +687 -0
- kolega_code/agent/tests/llm/test_tool_execution_ids.py +193 -0
- kolega_code/agent/tests/services/__init__.py +1 -0
- kolega_code/agent/tests/services/test_browser.py +447 -0
- kolega_code/agent/tests/services/test_browser_parity.py +353 -0
- kolega_code/agent/tests/services/test_file_system.py +699 -0
- kolega_code/agent/tests/services/test_sandbox_terminal_input.py +98 -0
- kolega_code/agent/tests/services/test_terminal.py +154 -0
- kolega_code/agent/tests/services/test_terminal_command_tracking.py +385 -0
- kolega_code/agent/tests/services/test_terminal_state_serializer.py +262 -0
- kolega_code/agent/tests/test_agent_tools_inventory.py +267 -0
- kolega_code/agent/tests/test_base_agent.py +1942 -0
- kolega_code/agent/tests/test_coder_attachments.py +330 -0
- kolega_code/agent/tests/test_coder_prompt_extensions.py +61 -0
- kolega_code/agent/tests/test_commands.py +179 -0
- kolega_code/agent/tests/test_duplicate_tool_results.py +556 -0
- kolega_code/agent/tests/test_empty_message_handling.py +48 -0
- kolega_code/agent/tests/test_general_agent.py +242 -0
- kolega_code/agent/tests/test_html.py +320 -0
- kolega_code/agent/tests/test_parallel_tool_calls.py +291 -0
- kolega_code/agent/tests/test_planning_agent.py +227 -0
- kolega_code/agent/tests/test_prompt_provider.py +271 -0
- kolega_code/agent/tests/test_tool_registry.py +102 -0
- kolega_code/agent/tests/test_tools.py +549 -0
- kolega_code/agent/tests/tool_backend/__init__.py +0 -0
- kolega_code/agent/tests/tool_backend/test_agent_tool.py +356 -0
- kolega_code/agent/tests/tool_backend/test_base_tool.py +147 -0
- kolega_code/agent/tests/tool_backend/test_browser_tool.py +335 -0
- kolega_code/agent/tests/tool_backend/test_build_tool.py +93 -0
- kolega_code/agent/tests/tool_backend/test_create_file_tool.py +115 -0
- kolega_code/agent/tests/tool_backend/test_glob_tool.py +196 -0
- kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +230 -0
- kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +292 -0
- kolega_code/agent/tests/tool_backend/test_read_file_tool.py +173 -0
- kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +115 -0
- kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +141 -0
- kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +174 -0
- kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +228 -0
- kolega_code/agent/tests/tool_backend/test_terminal_tool.py +482 -0
- kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +189 -0
- kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +445 -0
- kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +194 -0
- kolega_code/agent/tool_backend/agent_tool.py +414 -0
- kolega_code/agent/tool_backend/apply_edit_tool.py +98 -0
- kolega_code/agent/tool_backend/apply_patch_tool.py +514 -0
- kolega_code/agent/tool_backend/base_tool.py +217 -0
- kolega_code/agent/tool_backend/browser_tool.py +271 -0
- kolega_code/agent/tool_backend/build_tool.py +93 -0
- kolega_code/agent/tool_backend/create_file_tool.py +52 -0
- kolega_code/agent/tool_backend/glob_tool.py +323 -0
- kolega_code/agent/tool_backend/list_directory_tool.py +300 -0
- kolega_code/agent/tool_backend/memory_tool.py +79 -0
- kolega_code/agent/tool_backend/read_file_tool.py +119 -0
- kolega_code/agent/tool_backend/replace_entire_file_tool.py +40 -0
- kolega_code/agent/tool_backend/replace_lines_tool.py +97 -0
- kolega_code/agent/tool_backend/search_and_replace_tool.py +146 -0
- kolega_code/agent/tool_backend/search_codebase_tool.py +377 -0
- kolega_code/agent/tool_backend/streaming_tool.py +47 -0
- kolega_code/agent/tool_backend/terminal_tool.py +643 -0
- kolega_code/agent/tool_backend/think_hard_tool.py +211 -0
- kolega_code/agent/tool_backend/web_fetch_tool.py +205 -0
- kolega_code/agent/tools.py +1704 -0
- kolega_code/agent/utils/commands.py +94 -0
- kolega_code/cli/__init__.py +1 -0
- kolega_code/cli/app.py +2756 -0
- kolega_code/cli/config.py +280 -0
- kolega_code/cli/connection.py +49 -0
- kolega_code/cli/file_index.py +147 -0
- kolega_code/cli/main.py +564 -0
- kolega_code/cli/mentions.py +155 -0
- kolega_code/cli/messages.py +89 -0
- kolega_code/cli/provider_registry.py +96 -0
- kolega_code/cli/session_store.py +207 -0
- kolega_code/cli/settings.py +87 -0
- kolega_code/cli/skills.py +409 -0
- kolega_code/cli/slash_commands.py +108 -0
- kolega_code/cli/tests/__init__.py +1 -0
- kolega_code/cli/tests/test_app.py +4251 -0
- kolega_code/cli/tests/test_cli_config.py +171 -0
- kolega_code/cli/tests/test_connection.py +26 -0
- kolega_code/cli/tests/test_file_index.py +103 -0
- kolega_code/cli/tests/test_main.py +455 -0
- kolega_code/cli/tests/test_mentions.py +108 -0
- kolega_code/cli/tests/test_session_store.py +67 -0
- kolega_code/cli/tests/test_settings.py +62 -0
- kolega_code/cli/tests/test_skills.py +157 -0
- kolega_code/cli/tests/test_slash_commands.py +88 -0
- kolega_code/cli/theme.py +180 -0
- kolega_code/config.py +154 -0
- kolega_code/events.py +202 -0
- kolega_code/llm/client.py +300 -0
- kolega_code/llm/exceptions.py +285 -0
- kolega_code/llm/instrumented_client.py +520 -0
- kolega_code/llm/models.py +1368 -0
- kolega_code/llm/providers/__init__.py +0 -0
- kolega_code/llm/providers/anthropic.py +387 -0
- kolega_code/llm/providers/base.py +71 -0
- kolega_code/llm/providers/google.py +157 -0
- kolega_code/llm/providers/models.py +37 -0
- kolega_code/llm/providers/openai.py +363 -0
- kolega_code/llm/ratelimit.py +40 -0
- kolega_code/llm/specs.py +67 -0
- kolega_code/llm/tool_execution_ids.py +18 -0
- kolega_code/models/__init__.py +9 -0
- kolega_code/models/sandbox_terminal_state.py +47 -0
- kolega_code/runtime.py +50 -0
- kolega_code/sandbox/README.md +200 -0
- kolega_code/sandbox/__init__.py +21 -0
- kolega_code/sandbox/async_filesystem.py +475 -0
- kolega_code/sandbox/base.py +297 -0
- kolega_code/sandbox/browser.py +25 -0
- kolega_code/sandbox/event_loop.py +43 -0
- kolega_code/sandbox/filesystem.py +341 -0
- kolega_code/sandbox/local.py +118 -0
- kolega_code/sandbox/serializer.py +175 -0
- kolega_code/sandbox/terminal.py +868 -0
- kolega_code/sandbox/utils.py +216 -0
- kolega_code/services/base.py +255 -0
- kolega_code/services/browser.py +444 -0
- kolega_code/services/file_system.py +749 -0
- kolega_code/services/html.py +221 -0
- kolega_code/services/terminal.py +903 -0
- kolega_code/tools/__init__.py +22 -0
- kolega_code/tools/core.py +33 -0
- kolega_code/tools/definitions.py +81 -0
- kolega_code/tools/registry.py +73 -0
- kolega_code-0.1.0.dist-info/METADATA +157 -0
- kolega_code-0.1.0.dist-info/RECORD +171 -0
- kolega_code-0.1.0.dist-info/WHEEL +4 -0
- kolega_code-0.1.0.dist-info/entry_points.txt +2 -0
- kolega_code-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Utility functions for sandbox operations."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import shlex
|
|
5
|
+
from typing import List, Optional, Dict, Any
|
|
6
|
+
from .base import SandboxManager, ProjectManifest
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
WORKSPACE_PATH = "/home/user/workspace"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_git_status_output(status_output: str) -> List[str]:
|
|
15
|
+
"""Parse git status --porcelain output and return list of files to add.
|
|
16
|
+
|
|
17
|
+
This is a shared parsing function used by both direct E2B sandbox operations
|
|
18
|
+
and SandboxManager-based operations to ensure consistent git status parsing.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
status_output: Raw output from 'git -c core.quotePath=false status --porcelain'
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
List of file paths, excluding deleted files and handling renames correctly
|
|
25
|
+
"""
|
|
26
|
+
logger.debug(f"Parsing git status output: {status_output!r}")
|
|
27
|
+
|
|
28
|
+
if not status_output or not status_output.strip():
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
modified_files = []
|
|
32
|
+
for line in status_output.split("\n"):
|
|
33
|
+
if not line or len(line) < 3:
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
# Git status format: XY filename
|
|
37
|
+
# X = staging area status, Y = working tree status
|
|
38
|
+
# Status codes (per git-status --porcelain):
|
|
39
|
+
# ' ' = unmodified
|
|
40
|
+
# M = modified
|
|
41
|
+
# A = added
|
|
42
|
+
# D = deleted
|
|
43
|
+
# R = renamed
|
|
44
|
+
# C = copied
|
|
45
|
+
# U = updated but unmerged
|
|
46
|
+
# ? = untracked
|
|
47
|
+
# ! = ignored
|
|
48
|
+
status = line[:2] # First 2 characters are status codes
|
|
49
|
+
file_path = line[3:].strip() # Everything after "XY "
|
|
50
|
+
|
|
51
|
+
# Skip STAGED deletions (D at position 0) - these are already removed from git index
|
|
52
|
+
# These will fail with "pathspec did not match any files" error
|
|
53
|
+
# Working tree deletions (" D" at position 1, like " D", "MD", "AD") can still be staged with git add, so we include them
|
|
54
|
+
if status[0] == "D":
|
|
55
|
+
logger.debug(f"Skipping staged deletion (not in git index): {file_path}")
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# Handle renamed files: "R old -> new"
|
|
59
|
+
# Git quotes filenames containing " -> ": R "old -> a.txt" -> "new -> b.txt"
|
|
60
|
+
if status.startswith("R"):
|
|
61
|
+
logger.debug(f"Processing renamed file - status: {status!r}, file_path: {file_path!r}")
|
|
62
|
+
if " -> " in file_path:
|
|
63
|
+
# Find the separator " -> " that's NOT inside quotes
|
|
64
|
+
in_quotes = False
|
|
65
|
+
for i in range(len(file_path) - 4): # -4 for " -> " (4 chars)
|
|
66
|
+
if file_path[i] == '"':
|
|
67
|
+
in_quotes = not in_quotes
|
|
68
|
+
elif not in_quotes and file_path[i:i+4] == ' -> ':
|
|
69
|
+
# Found the real separator between old and new filenames
|
|
70
|
+
file_path = file_path[i+4:].strip()
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
# Remove quotes if present (git quotes filenames with special chars or " -> ")
|
|
74
|
+
if file_path.startswith('"') and file_path.endswith('"'):
|
|
75
|
+
file_path = file_path[1:-1]
|
|
76
|
+
# Unescape git's quote escaping (\" becomes ")
|
|
77
|
+
file_path = file_path.replace('\\"', '"')
|
|
78
|
+
|
|
79
|
+
modified_files.append(file_path)
|
|
80
|
+
|
|
81
|
+
return modified_files
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def get_modified_files_from_sandbox(sandbox_manager: SandboxManager, sandbox_id: str) -> List[str]:
|
|
85
|
+
"""
|
|
86
|
+
Get list of modified files from sandbox using git status.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
sandbox_manager: The sandbox manager instance
|
|
90
|
+
sandbox_id: ID of the sandbox
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of modified file paths
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
# Get the sandbox's terminal manager
|
|
97
|
+
terminal_manager = sandbox_manager.get_terminal_manager(sandbox_id)
|
|
98
|
+
|
|
99
|
+
# Run git status with core.quotePath=false to get raw filenames without escaping
|
|
100
|
+
# This prevents issues with special characters (e.g., em-dash — being escaped as \342\200\224)
|
|
101
|
+
result = await terminal_manager.run_command("git -c core.quotePath=false status --porcelain", cwd=WORKSPACE_PATH)
|
|
102
|
+
|
|
103
|
+
# Use shared parsing function
|
|
104
|
+
return parse_git_status_output(result)
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(f"Error getting modified files from sandbox {sandbox_id}: {e}")
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def get_git_diff_from_sandbox(
|
|
112
|
+
sandbox_manager: SandboxManager, sandbox_id: str, files: Optional[List[str]] = None
|
|
113
|
+
) -> str:
|
|
114
|
+
"""
|
|
115
|
+
Get git diff from sandbox.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
sandbox_manager: The sandbox manager instance
|
|
119
|
+
sandbox_id: ID of the sandbox
|
|
120
|
+
files: Optional list of specific files to diff
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Git diff output as string
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
# Get the sandbox's terminal manager
|
|
127
|
+
terminal_manager = sandbox_manager.get_terminal_manager(sandbox_id)
|
|
128
|
+
|
|
129
|
+
# Include untracked files in the diff without staging their contents.
|
|
130
|
+
try:
|
|
131
|
+
await terminal_manager.run_command("git add -N .", cwd=WORKSPACE_PATH)
|
|
132
|
+
except Exception as add_error:
|
|
133
|
+
logger.warning(f"Failed to mark untracked files for diff in sandbox {sandbox_id}: {add_error}")
|
|
134
|
+
|
|
135
|
+
# Build git diff command
|
|
136
|
+
if files:
|
|
137
|
+
# Diff specific files
|
|
138
|
+
files_arg = " ".join(shlex.quote(f) for f in files)
|
|
139
|
+
cmd = f"git diff HEAD -- {files_arg}"
|
|
140
|
+
else:
|
|
141
|
+
# Diff all changes
|
|
142
|
+
cmd = "git diff HEAD"
|
|
143
|
+
|
|
144
|
+
# Run git diff
|
|
145
|
+
result = await terminal_manager.run_command(cmd, cwd=WORKSPACE_PATH)
|
|
146
|
+
|
|
147
|
+
return result or ""
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logger.error(f"Error getting git diff from sandbox {sandbox_id}: {e}")
|
|
151
|
+
return ""
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def run_project_tests_in_sandbox(
|
|
155
|
+
sandbox_manager: SandboxManager, sandbox_id: str, config: Optional[dict] = None
|
|
156
|
+
) -> dict:
|
|
157
|
+
"""
|
|
158
|
+
Run project tests in sandbox based on manifest configuration.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
sandbox_manager: The sandbox manager instance
|
|
162
|
+
sandbox_id: ID of the sandbox
|
|
163
|
+
config: Optional project configuration
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Dictionary with test results
|
|
167
|
+
"""
|
|
168
|
+
try:
|
|
169
|
+
# Get the terminal manager
|
|
170
|
+
terminal_manager = sandbox_manager.get_terminal_manager(sandbox_id)
|
|
171
|
+
|
|
172
|
+
# Try to read project manifest
|
|
173
|
+
manifest_path = f"{WORKSPACE_PATH}/.kolega-manifest.yaml"
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
filesystem = sandbox_manager.get_filesystem(sandbox_id)
|
|
177
|
+
manifest_content = await filesystem.read_text(manifest_path)
|
|
178
|
+
manifest_data = yaml.safe_load(manifest_content)
|
|
179
|
+
manifest = ProjectManifest(**manifest_data)
|
|
180
|
+
test_commands = manifest.test_commands or []
|
|
181
|
+
except Exception:
|
|
182
|
+
# Fallback to common test commands if no manifest
|
|
183
|
+
test_commands = ["npm test", "pytest", "python -m pytest", "cargo test", "go test ./...", "mvn test"]
|
|
184
|
+
|
|
185
|
+
if not test_commands:
|
|
186
|
+
return {"success": False, "error": "No test commands found in project configuration"}
|
|
187
|
+
|
|
188
|
+
# Run test commands
|
|
189
|
+
results = []
|
|
190
|
+
for cmd in test_commands:
|
|
191
|
+
try:
|
|
192
|
+
logger.info(f"Running test command: {cmd}")
|
|
193
|
+
result = await terminal_manager.run_command(
|
|
194
|
+
cmd, cwd=WORKSPACE_PATH, timeout=300 # 5 minute timeout for tests
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Determine if command succeeded (this is a simple check)
|
|
198
|
+
success = "error" not in result.lower() and "failed" not in result.lower()
|
|
199
|
+
|
|
200
|
+
results.append({"command": cmd, "success": success, "output": result})
|
|
201
|
+
|
|
202
|
+
if success:
|
|
203
|
+
# If one test command succeeds, we can stop
|
|
204
|
+
break
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
results.append({"command": cmd, "success": False, "error": str(e)})
|
|
208
|
+
|
|
209
|
+
# Determine overall success
|
|
210
|
+
overall_success = any(r.get("success", False) for r in results)
|
|
211
|
+
|
|
212
|
+
return {"success": overall_success, "results": results}
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.error(f"Error running tests in sandbox {sandbox_id}: {e}")
|
|
216
|
+
return {"success": False, "error": str(e)}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TerminalManager(ABC):
|
|
6
|
+
"""
|
|
7
|
+
Abstract base class for terminal managers.
|
|
8
|
+
Implementations should handle creation, management, and cleanup of terminal instances.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
async def launch_terminal(self, terminal_id: Optional[str] = None, **terminal_kwargs) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Create a new terminal instance with the given ID or generate a random ID.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
terminal_id: Optional identifier for the terminal (random UUID if not provided)
|
|
18
|
+
**terminal_kwargs: Arguments to pass to terminal constructor
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
The ID of the created terminal
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
async def send_command(
|
|
26
|
+
self, term_id: str, command: str, purpose: Optional[str] = None, timeout: Optional[int] = None
|
|
27
|
+
) -> bool:
|
|
28
|
+
"""
|
|
29
|
+
Send a command to a specific terminal.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
term_id: ID of the terminal to send command to
|
|
33
|
+
command: The command to execute
|
|
34
|
+
purpose: Optional description of command purpose
|
|
35
|
+
timeout: Optional timeout in seconds (0 or None for no timeout)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
True if command was sent successfully
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
KeyError: If terminal_id doesn't exist
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def send_input(
|
|
46
|
+
self, term_id: str, text: str, submit: bool = True, command_id: Optional[str] = None
|
|
47
|
+
) -> bool:
|
|
48
|
+
"""
|
|
49
|
+
Send input to a command that is already running in a terminal.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
term_id: ID of the terminal to send input to
|
|
53
|
+
text: Input text to send
|
|
54
|
+
submit: Whether to append a newline before sending
|
|
55
|
+
command_id: Optional active command ID when the backend requires disambiguation
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if input was sent successfully
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
KeyError: If terminal_id doesn't exist
|
|
62
|
+
ValueError: If no running command can receive input
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
async def get_output(self, terminal_id: str, **kwargs) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Get output from a specific terminal.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
terminal_id: ID of the terminal to get output from
|
|
72
|
+
**kwargs: Arguments to pass to get output method
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Output from the specified terminal
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
KeyError: If terminal_id doesn't exist
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
async def close_terminal(self, terminal_id: str) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Close a specific terminal.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
terminal_id: ID of the terminal to close
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
KeyError: If terminal_id doesn't exist
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
@abstractmethod
|
|
94
|
+
async def close_all(self) -> None:
|
|
95
|
+
"""Close all terminal instances."""
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
async def list_terminals(self) -> Dict[str, Any]:
|
|
99
|
+
"""
|
|
100
|
+
Get information about all terminals.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Dictionary mapping terminal IDs to terminal information
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
async def run_command(self, command: str, cwd: Optional[str] = None, timeout: Optional[int] = None) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Run a command directly (convenience method for utilities).
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
command: Command to execute
|
|
113
|
+
cwd: Optional working directory
|
|
114
|
+
timeout: Optional timeout in seconds
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Command output as string
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class BrowserManager(ABC):
|
|
122
|
+
"""
|
|
123
|
+
Abstract base class for browser management.
|
|
124
|
+
Defines the interface for browser operations.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
async def launch_browser(self, url: str) -> str:
|
|
129
|
+
"""
|
|
130
|
+
Launch a new browser instance and navigate to the specified URL.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
url: The URL to navigate to
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Browser ID string
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
Exception: If browser launch fails
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
@abstractmethod
|
|
143
|
+
async def list_browsers(self) -> dict:
|
|
144
|
+
"""
|
|
145
|
+
Get information about all active browser instances.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Dictionary mapping browser IDs to browser information
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
@abstractmethod
|
|
152
|
+
async def get_browser_console_logs(
|
|
153
|
+
self,
|
|
154
|
+
browser_id: str,
|
|
155
|
+
max_logs: int = 50,
|
|
156
|
+
log_types: Optional[List[str]] = None,
|
|
157
|
+
minutes_back: Optional[int] = None,
|
|
158
|
+
max_chars: Optional[int] = 8000,
|
|
159
|
+
) -> dict:
|
|
160
|
+
"""
|
|
161
|
+
Get console logs from a specific browser with configurable filtering.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
browser_id: ID of the browser to get logs from
|
|
165
|
+
max_logs: Maximum number of logs to return (most recent)
|
|
166
|
+
log_types: List of log types to include (e.g., ['error', 'warning', 'assert'])
|
|
167
|
+
minutes_back: Only return logs from the last N minutes
|
|
168
|
+
max_chars: Maximum total character count for all log messages combined
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Dictionary containing filtered console logs and metadata
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
KeyError: If browser_id doesn't exist
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
@abstractmethod
|
|
178
|
+
async def get_browser_interactive_elements(self, browser_id: str) -> list:
|
|
179
|
+
"""
|
|
180
|
+
Get interactive elements from a specific browser.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
browser_id: ID of the browser to get elements from
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Dictionary containing current URL, title, and interactive elements
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
KeyError: If browser_id doesn't exist
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
@abstractmethod
|
|
193
|
+
async def take_browser_screenshot(self, browser_id: str) -> dict:
|
|
194
|
+
"""
|
|
195
|
+
Take a screenshot of a specific browser.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
browser_id: ID of the browser to take screenshot of
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Dictionary containing current URL, title, and base64-encoded screenshot
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
KeyError: If browser_id doesn't exist
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
@abstractmethod
|
|
208
|
+
async def interact_with_browser(self, browser_id: str, action: str, selector: str, text: str, scroll_px) -> dict:
|
|
209
|
+
"""
|
|
210
|
+
Interact with a specific browser.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
browser_id: ID of the browser to interact with
|
|
214
|
+
action: Type of interaction (click, type, scroll, navigate)
|
|
215
|
+
selector: CSS selector for the element to interact with
|
|
216
|
+
text: Text to type or URL to navigate to
|
|
217
|
+
scroll_px: Number of pixels to scroll
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Dictionary containing status and interaction details
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
KeyError: If browser_id doesn't exist
|
|
224
|
+
ValueError: If action is unknown
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
@abstractmethod
|
|
228
|
+
async def set_select_value(self, browser_id: str, selector: str, value: str) -> dict:
|
|
229
|
+
"""
|
|
230
|
+
Set the value of a select box in a specific browser.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
browser_id: ID of the browser to interact with
|
|
234
|
+
selector: CSS selector for the select element
|
|
235
|
+
value: The value to set for the select option
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Dictionary containing status, current URL, and selected value
|
|
239
|
+
|
|
240
|
+
Raises:
|
|
241
|
+
KeyError: If browser_id doesn't exist
|
|
242
|
+
ValueError: If the element is not a select box or value doesn't exist
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
@abstractmethod
|
|
246
|
+
async def close_browser(self, browser_id: str) -> None:
|
|
247
|
+
"""
|
|
248
|
+
Close a specific browser.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
browser_id: ID of the browser to close
|
|
252
|
+
|
|
253
|
+
Raises:
|
|
254
|
+
KeyError: If browser_id doesn't exist
|
|
255
|
+
"""
|