codemaster-cli 2.2.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.
- codemaster_cli-2.2.0.dist-info/METADATA +645 -0
- codemaster_cli-2.2.0.dist-info/RECORD +170 -0
- codemaster_cli-2.2.0.dist-info/WHEEL +4 -0
- codemaster_cli-2.2.0.dist-info/entry_points.txt +3 -0
- vibe/__init__.py +6 -0
- vibe/acp/__init__.py +0 -0
- vibe/acp/acp_agent_loop.py +746 -0
- vibe/acp/entrypoint.py +81 -0
- vibe/acp/tools/__init__.py +0 -0
- vibe/acp/tools/base.py +100 -0
- vibe/acp/tools/builtins/bash.py +134 -0
- vibe/acp/tools/builtins/read_file.py +54 -0
- vibe/acp/tools/builtins/search_replace.py +129 -0
- vibe/acp/tools/builtins/todo.py +65 -0
- vibe/acp/tools/builtins/write_file.py +98 -0
- vibe/acp/tools/session_update.py +118 -0
- vibe/acp/utils.py +213 -0
- vibe/cli/__init__.py +0 -0
- vibe/cli/autocompletion/__init__.py +0 -0
- vibe/cli/autocompletion/base.py +22 -0
- vibe/cli/autocompletion/path_completion.py +177 -0
- vibe/cli/autocompletion/slash_command.py +99 -0
- vibe/cli/cli.py +188 -0
- vibe/cli/clipboard.py +69 -0
- vibe/cli/commands.py +116 -0
- vibe/cli/entrypoint.py +163 -0
- vibe/cli/history_manager.py +91 -0
- vibe/cli/plan_offer/adapters/http_whoami_gateway.py +67 -0
- vibe/cli/plan_offer/decide_plan_offer.py +87 -0
- vibe/cli/plan_offer/ports/whoami_gateway.py +23 -0
- vibe/cli/terminal_setup.py +323 -0
- vibe/cli/textual_ui/__init__.py +0 -0
- vibe/cli/textual_ui/ansi_markdown.py +58 -0
- vibe/cli/textual_ui/app.py +1546 -0
- vibe/cli/textual_ui/app.tcss +1020 -0
- vibe/cli/textual_ui/external_editor.py +32 -0
- vibe/cli/textual_ui/handlers/__init__.py +5 -0
- vibe/cli/textual_ui/handlers/event_handler.py +147 -0
- vibe/cli/textual_ui/widgets/__init__.py +0 -0
- vibe/cli/textual_ui/widgets/approval_app.py +192 -0
- vibe/cli/textual_ui/widgets/banner/banner.py +85 -0
- vibe/cli/textual_ui/widgets/banner/petit_chat.py +195 -0
- vibe/cli/textual_ui/widgets/braille_renderer.py +58 -0
- vibe/cli/textual_ui/widgets/chat_input/__init__.py +7 -0
- vibe/cli/textual_ui/widgets/chat_input/body.py +214 -0
- vibe/cli/textual_ui/widgets/chat_input/completion_manager.py +58 -0
- vibe/cli/textual_ui/widgets/chat_input/completion_popup.py +43 -0
- vibe/cli/textual_ui/widgets/chat_input/container.py +195 -0
- vibe/cli/textual_ui/widgets/chat_input/text_area.py +365 -0
- vibe/cli/textual_ui/widgets/compact.py +41 -0
- vibe/cli/textual_ui/widgets/config_app.py +171 -0
- vibe/cli/textual_ui/widgets/context_progress.py +30 -0
- vibe/cli/textual_ui/widgets/load_more.py +43 -0
- vibe/cli/textual_ui/widgets/loading.py +201 -0
- vibe/cli/textual_ui/widgets/messages.py +277 -0
- vibe/cli/textual_ui/widgets/no_markup_static.py +11 -0
- vibe/cli/textual_ui/widgets/path_display.py +28 -0
- vibe/cli/textual_ui/widgets/proxy_setup_app.py +127 -0
- vibe/cli/textual_ui/widgets/question_app.py +496 -0
- vibe/cli/textual_ui/widgets/spinner.py +194 -0
- vibe/cli/textual_ui/widgets/status_message.py +76 -0
- vibe/cli/textual_ui/widgets/teleport_message.py +31 -0
- vibe/cli/textual_ui/widgets/tool_widgets.py +371 -0
- vibe/cli/textual_ui/widgets/tools.py +201 -0
- vibe/cli/textual_ui/windowing/__init__.py +29 -0
- vibe/cli/textual_ui/windowing/history.py +105 -0
- vibe/cli/textual_ui/windowing/history_windowing.py +71 -0
- vibe/cli/textual_ui/windowing/state.py +105 -0
- vibe/cli/update_notifier/__init__.py +47 -0
- vibe/cli/update_notifier/adapters/filesystem_update_cache_repository.py +59 -0
- vibe/cli/update_notifier/adapters/github_update_gateway.py +101 -0
- vibe/cli/update_notifier/adapters/pypi_update_gateway.py +107 -0
- vibe/cli/update_notifier/ports/update_cache_repository.py +16 -0
- vibe/cli/update_notifier/ports/update_gateway.py +53 -0
- vibe/cli/update_notifier/update.py +139 -0
- vibe/cli/update_notifier/whats_new.py +49 -0
- vibe/core/__init__.py +5 -0
- vibe/core/agent_loop.py +1075 -0
- vibe/core/agents/__init__.py +31 -0
- vibe/core/agents/manager.py +165 -0
- vibe/core/agents/models.py +122 -0
- vibe/core/auth/__init__.py +6 -0
- vibe/core/auth/crypto.py +137 -0
- vibe/core/auth/github.py +178 -0
- vibe/core/autocompletion/__init__.py +0 -0
- vibe/core/autocompletion/completers.py +257 -0
- vibe/core/autocompletion/file_indexer/__init__.py +10 -0
- vibe/core/autocompletion/file_indexer/ignore_rules.py +156 -0
- vibe/core/autocompletion/file_indexer/indexer.py +179 -0
- vibe/core/autocompletion/file_indexer/store.py +169 -0
- vibe/core/autocompletion/file_indexer/watcher.py +71 -0
- vibe/core/autocompletion/fuzzy.py +189 -0
- vibe/core/autocompletion/path_prompt.py +108 -0
- vibe/core/autocompletion/path_prompt_adapter.py +149 -0
- vibe/core/config.py +673 -0
- vibe/core/config_PATCH_INSTRUCTIONS.md +77 -0
- vibe/core/llm/__init__.py +0 -0
- vibe/core/llm/backend/anthropic.py +630 -0
- vibe/core/llm/backend/base.py +38 -0
- vibe/core/llm/backend/factory.py +7 -0
- vibe/core/llm/backend/generic.py +425 -0
- vibe/core/llm/backend/mistral.py +381 -0
- vibe/core/llm/backend/vertex.py +115 -0
- vibe/core/llm/exceptions.py +195 -0
- vibe/core/llm/format.py +184 -0
- vibe/core/llm/message_utils.py +24 -0
- vibe/core/llm/types.py +120 -0
- vibe/core/middleware.py +209 -0
- vibe/core/output_formatters.py +85 -0
- vibe/core/paths/__init__.py +0 -0
- vibe/core/paths/config_paths.py +68 -0
- vibe/core/paths/global_paths.py +40 -0
- vibe/core/programmatic.py +56 -0
- vibe/core/prompts/__init__.py +32 -0
- vibe/core/prompts/cli.md +111 -0
- vibe/core/prompts/compact.md +48 -0
- vibe/core/prompts/dangerous_directory.md +5 -0
- vibe/core/prompts/explore.md +50 -0
- vibe/core/prompts/project_context.md +8 -0
- vibe/core/prompts/tests.md +1 -0
- vibe/core/proxy_setup.py +65 -0
- vibe/core/session/session_loader.py +222 -0
- vibe/core/session/session_logger.py +318 -0
- vibe/core/session/session_migration.py +41 -0
- vibe/core/skills/__init__.py +7 -0
- vibe/core/skills/manager.py +132 -0
- vibe/core/skills/models.py +92 -0
- vibe/core/skills/parser.py +39 -0
- vibe/core/system_prompt.py +466 -0
- vibe/core/telemetry/__init__.py +0 -0
- vibe/core/telemetry/send.py +185 -0
- vibe/core/teleport/errors.py +9 -0
- vibe/core/teleport/git.py +196 -0
- vibe/core/teleport/nuage.py +180 -0
- vibe/core/teleport/teleport.py +208 -0
- vibe/core/teleport/types.py +54 -0
- vibe/core/tools/base.py +336 -0
- vibe/core/tools/builtins/ask_user_question.py +134 -0
- vibe/core/tools/builtins/bash.py +357 -0
- vibe/core/tools/builtins/grep.py +310 -0
- vibe/core/tools/builtins/prompts/__init__.py +0 -0
- vibe/core/tools/builtins/prompts/ask_user_question.md +84 -0
- vibe/core/tools/builtins/prompts/bash.md +73 -0
- vibe/core/tools/builtins/prompts/grep.md +4 -0
- vibe/core/tools/builtins/prompts/read_file.md +13 -0
- vibe/core/tools/builtins/prompts/search_replace.md +43 -0
- vibe/core/tools/builtins/prompts/task.md +24 -0
- vibe/core/tools/builtins/prompts/todo.md +199 -0
- vibe/core/tools/builtins/prompts/write_file.md +42 -0
- vibe/core/tools/builtins/read_file.py +222 -0
- vibe/core/tools/builtins/search_replace.py +456 -0
- vibe/core/tools/builtins/task.py +154 -0
- vibe/core/tools/builtins/todo.py +134 -0
- vibe/core/tools/builtins/write_file.py +160 -0
- vibe/core/tools/manager.py +341 -0
- vibe/core/tools/mcp.py +397 -0
- vibe/core/tools/ui.py +68 -0
- vibe/core/trusted_folders.py +86 -0
- vibe/core/types.py +405 -0
- vibe/core/utils.py +396 -0
- vibe/setup/onboarding/__init__.py +39 -0
- vibe/setup/onboarding/base.py +14 -0
- vibe/setup/onboarding/onboarding.tcss +134 -0
- vibe/setup/onboarding/screens/__init__.py +5 -0
- vibe/setup/onboarding/screens/api_key.py +200 -0
- vibe/setup/onboarding/screens/provider_selection.py +87 -0
- vibe/setup/onboarding/screens/welcome.py +136 -0
- vibe/setup/trusted_folders/trust_folder_dialog.py +180 -0
- vibe/setup/trusted_folders/trust_folder_dialog.tcss +83 -0
- vibe/whats_new.md +5 -0
vibe/acp/entrypoint.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from vibe import __version__
|
|
9
|
+
from vibe.core.config import VibeConfig
|
|
10
|
+
from vibe.core.paths.config_paths import CONFIG_FILE, HISTORY_FILE, unlock_config_paths
|
|
11
|
+
from vibe.core.utils import logger
|
|
12
|
+
|
|
13
|
+
# Configure line buffering for subprocess communication
|
|
14
|
+
sys.stdout.reconfigure(line_buffering=True) # pyright: ignore[reportAttributeAccessIssue]
|
|
15
|
+
sys.stderr.reconfigure(line_buffering=True) # pyright: ignore[reportAttributeAccessIssue]
|
|
16
|
+
sys.stdin.reconfigure(line_buffering=True) # pyright: ignore[reportAttributeAccessIssue]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Arguments:
|
|
21
|
+
setup: bool
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_arguments() -> Arguments:
|
|
25
|
+
parser = argparse.ArgumentParser(description="Run codeMaster in ACP mode")
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-v", "--version", action="version", version=f"%(prog)s {__version__}"
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument("--setup", action="store_true", help="Setup API key and exit")
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
return Arguments(setup=args.setup)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def bootstrap_config_files() -> None:
|
|
35
|
+
if not CONFIG_FILE.path.exists():
|
|
36
|
+
try:
|
|
37
|
+
VibeConfig.save_updates(VibeConfig.create_default())
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.error(f"Could not create default config file: {e}")
|
|
40
|
+
raise
|
|
41
|
+
|
|
42
|
+
if not HISTORY_FILE.path.exists():
|
|
43
|
+
try:
|
|
44
|
+
HISTORY_FILE.path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
HISTORY_FILE.path.write_text("Hello Vibe!\n", "utf-8")
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.error(f"Could not create history file: {e}")
|
|
48
|
+
raise
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def handle_debug_mode() -> None:
|
|
52
|
+
if os.environ.get("DEBUG_MODE") != "true":
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
import debugpy
|
|
57
|
+
except ImportError:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
debugpy.listen(("localhost", 5678))
|
|
61
|
+
# uncomment this to wait for the debugger to attach
|
|
62
|
+
# debugpy.wait_for_client()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main() -> None:
|
|
66
|
+
handle_debug_mode()
|
|
67
|
+
unlock_config_paths()
|
|
68
|
+
|
|
69
|
+
from vibe.acp.acp_agent_loop import run_acp_server
|
|
70
|
+
from vibe.setup.onboarding import run_onboarding
|
|
71
|
+
|
|
72
|
+
bootstrap_config_files()
|
|
73
|
+
args = parse_arguments()
|
|
74
|
+
if args.setup:
|
|
75
|
+
run_onboarding()
|
|
76
|
+
sys.exit(0)
|
|
77
|
+
run_acp_server()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|
|
File without changes
|
vibe/acp/tools/base.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from typing import Annotated, Protocol, cast, runtime_checkable
|
|
5
|
+
|
|
6
|
+
from acp import Client
|
|
7
|
+
from acp.helpers import SessionUpdate, ToolCallContentVariant
|
|
8
|
+
from acp.schema import ToolCallProgress
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field, SkipValidation
|
|
10
|
+
|
|
11
|
+
from vibe.core.tools.base import BaseTool, ToolError
|
|
12
|
+
from vibe.core.tools.manager import ToolManager
|
|
13
|
+
from vibe.core.types import ToolCallEvent, ToolResultEvent
|
|
14
|
+
from vibe.core.utils import logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class ToolCallSessionUpdateProtocol(Protocol):
|
|
19
|
+
@classmethod
|
|
20
|
+
def tool_call_session_update(cls, event: ToolCallEvent) -> SessionUpdate | None: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@runtime_checkable
|
|
24
|
+
class ToolResultSessionUpdateProtocol(Protocol):
|
|
25
|
+
@classmethod
|
|
26
|
+
def tool_result_session_update(
|
|
27
|
+
cls, event: ToolResultEvent
|
|
28
|
+
) -> SessionUpdate | None: ...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AcpToolState(BaseModel):
|
|
32
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
33
|
+
|
|
34
|
+
client: Annotated[Client | None, SkipValidation] = Field(
|
|
35
|
+
default=None, description="ACP Client"
|
|
36
|
+
)
|
|
37
|
+
session_id: str | None = Field(default=None, description="Current ACP session ID")
|
|
38
|
+
tool_call_id: str | None = Field(
|
|
39
|
+
default=None, description="Current ACP tool call ID"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BaseAcpTool[ToolState: AcpToolState](BaseTool):
|
|
44
|
+
state: ToolState
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def get_tool_instance(
|
|
48
|
+
cls, tool_name: str, tool_manager: ToolManager
|
|
49
|
+
) -> BaseAcpTool[AcpToolState]:
|
|
50
|
+
return cast(BaseAcpTool[AcpToolState], tool_manager.get(tool_name))
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def update_tool_state(
|
|
54
|
+
cls,
|
|
55
|
+
*,
|
|
56
|
+
tool_manager: ToolManager,
|
|
57
|
+
client: Client | None,
|
|
58
|
+
session_id: str | None,
|
|
59
|
+
tool_call_id: str | None,
|
|
60
|
+
) -> None:
|
|
61
|
+
tool_instance = cls.get_tool_instance(cls.get_name(), tool_manager)
|
|
62
|
+
tool_instance.state.client = client
|
|
63
|
+
tool_instance.state.session_id = session_id
|
|
64
|
+
tool_instance.state.tool_call_id = tool_call_id
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def _get_tool_state_class(cls) -> type[ToolState]: ...
|
|
69
|
+
|
|
70
|
+
def _load_state(self) -> tuple[Client, str, str | None]:
|
|
71
|
+
if self.state.client is None:
|
|
72
|
+
raise ToolError(
|
|
73
|
+
"Client not available in tool state. This tool can only be used within an ACP session."
|
|
74
|
+
)
|
|
75
|
+
if self.state.session_id is None:
|
|
76
|
+
raise ToolError(
|
|
77
|
+
"Session ID not available in tool state. This tool can only be used within an ACP session."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return self.state.client, self.state.session_id, self.state.tool_call_id
|
|
81
|
+
|
|
82
|
+
async def _send_in_progress_session_update(
|
|
83
|
+
self, content: list[ToolCallContentVariant] | None = None
|
|
84
|
+
) -> None:
|
|
85
|
+
client, session_id, tool_call_id = self._load_state()
|
|
86
|
+
if tool_call_id is None:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
await client.session_update(
|
|
91
|
+
session_id=session_id,
|
|
92
|
+
update=ToolCallProgress(
|
|
93
|
+
session_update="tool_call_update",
|
|
94
|
+
tool_call_id=tool_call_id,
|
|
95
|
+
status="in_progress",
|
|
96
|
+
content=content,
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.error(f"Failed to update session: {e!r}")
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import AsyncGenerator
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from acp.schema import (
|
|
8
|
+
TerminalToolCallContent,
|
|
9
|
+
ToolCallProgress,
|
|
10
|
+
ToolCallStart,
|
|
11
|
+
WaitForTerminalExitResponse,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from vibe import VIBE_ROOT
|
|
15
|
+
from vibe.acp.tools.base import AcpToolState, BaseAcpTool
|
|
16
|
+
from vibe.core.tools.base import BaseToolState, InvokeContext, ToolError
|
|
17
|
+
from vibe.core.tools.builtins.bash import Bash as CoreBashTool, BashArgs, BashResult
|
|
18
|
+
from vibe.core.types import ToolCallEvent, ToolResultEvent, ToolStreamEvent
|
|
19
|
+
from vibe.core.utils import logger
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AcpBashState(BaseToolState, AcpToolState):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Bash(CoreBashTool, BaseAcpTool[AcpBashState]):
|
|
27
|
+
prompt_path = VIBE_ROOT / "core" / "tools" / "builtins" / "prompts" / "bash.md"
|
|
28
|
+
state: AcpBashState
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def _get_tool_state_class(cls) -> type[AcpBashState]:
|
|
32
|
+
return AcpBashState
|
|
33
|
+
|
|
34
|
+
async def run(
|
|
35
|
+
self, args: BashArgs, ctx: InvokeContext | None = None
|
|
36
|
+
) -> AsyncGenerator[ToolStreamEvent | BashResult, None]:
|
|
37
|
+
client, session_id, _ = self._load_state()
|
|
38
|
+
|
|
39
|
+
timeout = args.timeout or self.config.default_timeout
|
|
40
|
+
max_bytes = self.config.max_output_bytes
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
terminal = await client.create_terminal(
|
|
44
|
+
session_id=session_id,
|
|
45
|
+
command=args.command,
|
|
46
|
+
cwd=str(Path.cwd()),
|
|
47
|
+
output_byte_limit=max_bytes,
|
|
48
|
+
)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
raise ToolError(f"Failed to create terminal: {e!r}") from e
|
|
51
|
+
|
|
52
|
+
terminal_id = terminal.terminal_id
|
|
53
|
+
|
|
54
|
+
await self._send_in_progress_session_update([
|
|
55
|
+
TerminalToolCallContent(type="terminal", terminal_id=terminal_id)
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
exit_response = await self._wait_for_terminal_exit(
|
|
60
|
+
terminal_id=terminal_id, timeout=timeout, command=args.command
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
output_response = await client.terminal_output(
|
|
64
|
+
session_id=session_id, terminal_id=terminal_id
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
yield self._build_result(
|
|
68
|
+
command=args.command,
|
|
69
|
+
stdout=output_response.output,
|
|
70
|
+
stderr="",
|
|
71
|
+
returncode=exit_response.exit_code or 0,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
finally:
|
|
75
|
+
try:
|
|
76
|
+
await client.release_terminal(
|
|
77
|
+
session_id=session_id, terminal_id=terminal_id
|
|
78
|
+
)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.error(f"Failed to release terminal: {e!r}")
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def get_summary(cls, args: BashArgs) -> str:
|
|
84
|
+
summary = f"{args.command}"
|
|
85
|
+
if args.timeout:
|
|
86
|
+
summary += f" (timeout {args.timeout}s)"
|
|
87
|
+
|
|
88
|
+
return summary
|
|
89
|
+
|
|
90
|
+
async def _wait_for_terminal_exit(
|
|
91
|
+
self, terminal_id: str, timeout: int, command: str
|
|
92
|
+
) -> WaitForTerminalExitResponse:
|
|
93
|
+
client, session_id, _ = self._load_state()
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
return await asyncio.wait_for(
|
|
97
|
+
client.wait_for_terminal_exit(
|
|
98
|
+
session_id=session_id, terminal_id=terminal_id
|
|
99
|
+
),
|
|
100
|
+
timeout=timeout,
|
|
101
|
+
)
|
|
102
|
+
except TimeoutError:
|
|
103
|
+
try:
|
|
104
|
+
await client.kill_terminal(
|
|
105
|
+
session_id=session_id, terminal_id=terminal_id
|
|
106
|
+
)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Failed to kill terminal: {e!r}")
|
|
109
|
+
|
|
110
|
+
raise self._build_timeout_error(command, timeout)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def tool_call_session_update(cls, event: ToolCallEvent) -> ToolCallStart:
|
|
114
|
+
if not isinstance(event.args, BashArgs):
|
|
115
|
+
raise ValueError(f"Unexpected tool args: {event.args}")
|
|
116
|
+
|
|
117
|
+
return ToolCallStart(
|
|
118
|
+
session_update="tool_call",
|
|
119
|
+
title=Bash.get_summary(event.args),
|
|
120
|
+
content=None,
|
|
121
|
+
tool_call_id=event.tool_call_id,
|
|
122
|
+
kind="execute",
|
|
123
|
+
raw_input=event.args.model_dump_json(),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def tool_result_session_update(
|
|
128
|
+
cls, event: ToolResultEvent
|
|
129
|
+
) -> ToolCallProgress | None:
|
|
130
|
+
return ToolCallProgress(
|
|
131
|
+
session_update="tool_call_update",
|
|
132
|
+
tool_call_id=event.tool_call_id,
|
|
133
|
+
status="failed" if event.error else "completed",
|
|
134
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from vibe import VIBE_ROOT
|
|
6
|
+
from vibe.acp.tools.base import AcpToolState, BaseAcpTool
|
|
7
|
+
from vibe.core.tools.base import ToolError
|
|
8
|
+
from vibe.core.tools.builtins.read_file import (
|
|
9
|
+
ReadFile as CoreReadFileTool,
|
|
10
|
+
ReadFileArgs,
|
|
11
|
+
ReadFileResult,
|
|
12
|
+
ReadFileState,
|
|
13
|
+
_ReadResult,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
ReadFileResult = ReadFileResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AcpReadFileState(ReadFileState, AcpToolState):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ReadFile(CoreReadFileTool, BaseAcpTool[AcpReadFileState]):
|
|
24
|
+
state: AcpReadFileState
|
|
25
|
+
prompt_path = VIBE_ROOT / "core" / "tools" / "builtins" / "prompts" / "read_file.md"
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def _get_tool_state_class(cls) -> type[AcpReadFileState]:
|
|
29
|
+
return AcpReadFileState
|
|
30
|
+
|
|
31
|
+
async def _read_file(self, args: ReadFileArgs, file_path: Path) -> _ReadResult:
|
|
32
|
+
client, session_id, _ = self._load_state()
|
|
33
|
+
|
|
34
|
+
line = args.offset + 1 if args.offset > 0 else None
|
|
35
|
+
limit = args.limit
|
|
36
|
+
|
|
37
|
+
await self._send_in_progress_session_update()
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
response = await client.read_text_file(
|
|
41
|
+
session_id=session_id, path=str(file_path), line=line, limit=limit
|
|
42
|
+
)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
raise ToolError(f"Error reading {file_path}: {e}") from e
|
|
45
|
+
|
|
46
|
+
content_lines = response.content.splitlines(keepends=True)
|
|
47
|
+
lines_read = len(content_lines)
|
|
48
|
+
bytes_read = sum(len(line.encode("utf-8")) for line in content_lines)
|
|
49
|
+
|
|
50
|
+
was_truncated = args.limit is not None and lines_read >= args.limit
|
|
51
|
+
|
|
52
|
+
return _ReadResult(
|
|
53
|
+
lines=content_lines, bytes_read=bytes_read, was_truncated=was_truncated
|
|
54
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from acp.helpers import SessionUpdate
|
|
6
|
+
from acp.schema import (
|
|
7
|
+
FileEditToolCallContent,
|
|
8
|
+
ToolCallLocation,
|
|
9
|
+
ToolCallProgress,
|
|
10
|
+
ToolCallStart,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from vibe import VIBE_ROOT
|
|
14
|
+
from vibe.acp.tools.base import AcpToolState, BaseAcpTool
|
|
15
|
+
from vibe.core.tools.base import ToolError
|
|
16
|
+
from vibe.core.tools.builtins.search_replace import (
|
|
17
|
+
SearchReplace as CoreSearchReplaceTool,
|
|
18
|
+
SearchReplaceArgs,
|
|
19
|
+
SearchReplaceResult,
|
|
20
|
+
SearchReplaceState,
|
|
21
|
+
)
|
|
22
|
+
from vibe.core.types import ToolCallEvent, ToolResultEvent
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AcpSearchReplaceState(SearchReplaceState, AcpToolState):
|
|
26
|
+
file_backup_content: str | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SearchReplace(CoreSearchReplaceTool, BaseAcpTool[AcpSearchReplaceState]):
|
|
30
|
+
state: AcpSearchReplaceState
|
|
31
|
+
prompt_path = (
|
|
32
|
+
VIBE_ROOT / "core" / "tools" / "builtins" / "prompts" / "search_replace.md"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def _get_tool_state_class(cls) -> type[AcpSearchReplaceState]:
|
|
37
|
+
return AcpSearchReplaceState
|
|
38
|
+
|
|
39
|
+
async def _read_file(self, file_path: Path) -> str:
|
|
40
|
+
client, session_id, _ = self._load_state()
|
|
41
|
+
|
|
42
|
+
await self._send_in_progress_session_update()
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
response = await client.read_text_file(
|
|
46
|
+
session_id=session_id, path=str(file_path)
|
|
47
|
+
)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
raise ToolError(f"Unexpected error reading {file_path}: {e}") from e
|
|
50
|
+
|
|
51
|
+
self.state.file_backup_content = response.content
|
|
52
|
+
return response.content
|
|
53
|
+
|
|
54
|
+
async def _backup_file(self, file_path: Path) -> None:
|
|
55
|
+
if self.state.file_backup_content is None:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
await self._write_file(
|
|
59
|
+
file_path.with_suffix(file_path.suffix + ".bak"),
|
|
60
|
+
self.state.file_backup_content,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
async def _write_file(self, file_path: Path, content: str) -> None:
|
|
64
|
+
client, session_id, _ = self._load_state()
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
await client.write_text_file(
|
|
68
|
+
session_id=session_id, path=str(file_path), content=content
|
|
69
|
+
)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
raise ToolError(f"Error writing {file_path}: {e}") from e
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def tool_call_session_update(cls, event: ToolCallEvent) -> SessionUpdate | None:
|
|
75
|
+
args = event.args
|
|
76
|
+
if not isinstance(args, SearchReplaceArgs):
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
blocks = cls._parse_search_replace_blocks(args.content)
|
|
80
|
+
|
|
81
|
+
return ToolCallStart(
|
|
82
|
+
session_update="tool_call",
|
|
83
|
+
title=cls.get_call_display(event).summary,
|
|
84
|
+
tool_call_id=event.tool_call_id,
|
|
85
|
+
kind="edit",
|
|
86
|
+
content=[
|
|
87
|
+
FileEditToolCallContent(
|
|
88
|
+
type="diff",
|
|
89
|
+
path=args.file_path,
|
|
90
|
+
old_text=block.search,
|
|
91
|
+
new_text=block.replace,
|
|
92
|
+
)
|
|
93
|
+
for block in blocks
|
|
94
|
+
],
|
|
95
|
+
locations=[ToolCallLocation(path=args.file_path)],
|
|
96
|
+
raw_input=args.model_dump_json(),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def tool_result_session_update(cls, event: ToolResultEvent) -> SessionUpdate | None:
|
|
101
|
+
if event.error:
|
|
102
|
+
return ToolCallProgress(
|
|
103
|
+
session_update="tool_call_update",
|
|
104
|
+
tool_call_id=event.tool_call_id,
|
|
105
|
+
status="failed",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
result = event.result
|
|
109
|
+
if not isinstance(result, SearchReplaceResult):
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
blocks = cls._parse_search_replace_blocks(result.content)
|
|
113
|
+
|
|
114
|
+
return ToolCallProgress(
|
|
115
|
+
session_update="tool_call_update",
|
|
116
|
+
tool_call_id=event.tool_call_id,
|
|
117
|
+
status="completed",
|
|
118
|
+
content=[
|
|
119
|
+
FileEditToolCallContent(
|
|
120
|
+
type="diff",
|
|
121
|
+
path=result.file,
|
|
122
|
+
old_text=block.search,
|
|
123
|
+
new_text=block.replace,
|
|
124
|
+
)
|
|
125
|
+
for block in blocks
|
|
126
|
+
],
|
|
127
|
+
locations=[ToolCallLocation(path=result.file)],
|
|
128
|
+
raw_output=result.model_dump_json(),
|
|
129
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from acp.helpers import SessionUpdate
|
|
6
|
+
from acp.schema import AgentPlanUpdate, PlanEntry, PlanEntryPriority, PlanEntryStatus
|
|
7
|
+
|
|
8
|
+
from vibe import VIBE_ROOT
|
|
9
|
+
from vibe.acp.tools.base import AcpToolState, BaseAcpTool
|
|
10
|
+
from vibe.core.tools.builtins.todo import (
|
|
11
|
+
Todo as CoreTodoTool,
|
|
12
|
+
TodoArgs,
|
|
13
|
+
TodoPriority,
|
|
14
|
+
TodoResult,
|
|
15
|
+
TodoState,
|
|
16
|
+
TodoStatus,
|
|
17
|
+
)
|
|
18
|
+
from vibe.core.types import ToolCallEvent, ToolResultEvent
|
|
19
|
+
|
|
20
|
+
TodoArgs = TodoArgs
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AcpTodoState(TodoState, AcpToolState):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Todo(CoreTodoTool, BaseAcpTool[AcpTodoState]):
|
|
28
|
+
state: AcpTodoState
|
|
29
|
+
prompt_path = VIBE_ROOT / "core" / "tools" / "builtins" / "prompts" / "todo.md"
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def _get_tool_state_class(cls) -> type[AcpTodoState]:
|
|
33
|
+
return AcpTodoState
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def tool_call_session_update(cls, event: ToolCallEvent) -> SessionUpdate | None:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def tool_result_session_update(cls, event: ToolResultEvent) -> SessionUpdate | None:
|
|
41
|
+
result = cast(TodoResult, event.result)
|
|
42
|
+
todos = [todo for todo in result.todos if todo.status != TodoStatus.CANCELLED]
|
|
43
|
+
matched_status: dict[TodoStatus, PlanEntryStatus] = {
|
|
44
|
+
TodoStatus.PENDING: "pending",
|
|
45
|
+
TodoStatus.IN_PROGRESS: "in_progress",
|
|
46
|
+
TodoStatus.COMPLETED: "completed",
|
|
47
|
+
}
|
|
48
|
+
matched_priority: dict[TodoPriority, PlanEntryPriority] = {
|
|
49
|
+
TodoPriority.LOW: "low",
|
|
50
|
+
TodoPriority.MEDIUM: "medium",
|
|
51
|
+
TodoPriority.HIGH: "high",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
update = AgentPlanUpdate(
|
|
55
|
+
session_update="plan",
|
|
56
|
+
entries=[
|
|
57
|
+
PlanEntry(
|
|
58
|
+
content=todo.content,
|
|
59
|
+
status=matched_status[todo.status],
|
|
60
|
+
priority=matched_priority[todo.priority],
|
|
61
|
+
)
|
|
62
|
+
for todo in todos
|
|
63
|
+
],
|
|
64
|
+
)
|
|
65
|
+
return update
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from acp.helpers import SessionUpdate
|
|
6
|
+
from acp.schema import (
|
|
7
|
+
FileEditToolCallContent,
|
|
8
|
+
ToolCallLocation,
|
|
9
|
+
ToolCallProgress,
|
|
10
|
+
ToolCallStart,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from vibe import VIBE_ROOT
|
|
14
|
+
from vibe.acp.tools.base import AcpToolState, BaseAcpTool
|
|
15
|
+
from vibe.core.tools.base import ToolError
|
|
16
|
+
from vibe.core.tools.builtins.write_file import (
|
|
17
|
+
WriteFile as CoreWriteFileTool,
|
|
18
|
+
WriteFileArgs,
|
|
19
|
+
WriteFileResult,
|
|
20
|
+
WriteFileState,
|
|
21
|
+
)
|
|
22
|
+
from vibe.core.types import ToolCallEvent, ToolResultEvent
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AcpWriteFileState(WriteFileState, AcpToolState):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WriteFile(CoreWriteFileTool, BaseAcpTool[AcpWriteFileState]):
|
|
30
|
+
state: AcpWriteFileState
|
|
31
|
+
prompt_path = (
|
|
32
|
+
VIBE_ROOT / "core" / "tools" / "builtins" / "prompts" / "write_file.md"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def _get_tool_state_class(cls) -> type[AcpWriteFileState]:
|
|
37
|
+
return AcpWriteFileState
|
|
38
|
+
|
|
39
|
+
async def _write_file(self, args: WriteFileArgs, file_path: Path) -> None:
|
|
40
|
+
client, session_id, _ = self._load_state()
|
|
41
|
+
|
|
42
|
+
await self._send_in_progress_session_update()
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
await client.write_text_file(
|
|
46
|
+
session_id=session_id, path=str(file_path), content=args.content
|
|
47
|
+
)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
raise ToolError(f"Error writing {file_path}: {e}") from e
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def tool_call_session_update(cls, event: ToolCallEvent) -> SessionUpdate | None:
|
|
53
|
+
args = event.args
|
|
54
|
+
if not isinstance(args, WriteFileArgs):
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
return ToolCallStart(
|
|
58
|
+
session_update="tool_call",
|
|
59
|
+
title=cls.get_call_display(event).summary,
|
|
60
|
+
tool_call_id=event.tool_call_id,
|
|
61
|
+
kind="edit",
|
|
62
|
+
content=[
|
|
63
|
+
FileEditToolCallContent(
|
|
64
|
+
type="diff", path=args.path, old_text=None, new_text=args.content
|
|
65
|
+
)
|
|
66
|
+
],
|
|
67
|
+
locations=[ToolCallLocation(path=args.path)],
|
|
68
|
+
raw_input=args.model_dump_json(),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def tool_result_session_update(cls, event: ToolResultEvent) -> SessionUpdate | None:
|
|
73
|
+
if event.error:
|
|
74
|
+
return ToolCallProgress(
|
|
75
|
+
session_update="tool_call_update",
|
|
76
|
+
tool_call_id=event.tool_call_id,
|
|
77
|
+
status="failed",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
result = event.result
|
|
81
|
+
if not isinstance(result, WriteFileResult):
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
return ToolCallProgress(
|
|
85
|
+
session_update="tool_call_update",
|
|
86
|
+
tool_call_id=event.tool_call_id,
|
|
87
|
+
status="completed",
|
|
88
|
+
content=[
|
|
89
|
+
FileEditToolCallContent(
|
|
90
|
+
type="diff",
|
|
91
|
+
path=result.path,
|
|
92
|
+
old_text=None,
|
|
93
|
+
new_text=result.content,
|
|
94
|
+
)
|
|
95
|
+
],
|
|
96
|
+
locations=[ToolCallLocation(path=result.path)],
|
|
97
|
+
raw_output=result.model_dump_json(),
|
|
98
|
+
)
|