v2.7.6 (#600)
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Paul Cacheux <paul.cacheux@mistral.ai> Co-authored-by: Quentin <quentin.torroba@mistral.ai> Co-authored-by: Simon <80467011+sorgfresser@users.noreply.github.com> Co-authored-by: angelapopopo <angele.lenglemetz@mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
e1a25caa52
commit
95336528f6
59 changed files with 3992 additions and 403 deletions
|
|
@ -33,6 +33,7 @@ from acp.schema import (
|
|||
AvailableCommandInput,
|
||||
ClientCapabilities,
|
||||
CloseSessionResponse,
|
||||
ConfigOptionUpdate,
|
||||
ContentToolCallContent,
|
||||
Cost,
|
||||
EnvVarAuthMethod,
|
||||
|
|
@ -44,6 +45,8 @@ from acp.schema import (
|
|||
PromptCapabilities,
|
||||
ResumeSessionResponse,
|
||||
SessionCapabilities,
|
||||
SessionConfigOptionBoolean,
|
||||
SessionConfigOptionSelect,
|
||||
SessionInfo,
|
||||
SessionListCapabilities,
|
||||
SetSessionConfigOptionResponse,
|
||||
|
|
@ -57,10 +60,11 @@ from acp.schema import (
|
|||
Usage,
|
||||
UsageUpdate,
|
||||
)
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from pydantic import BaseModel, ConfigDict, SkipValidation
|
||||
|
||||
from vibe import VIBE_ROOT, __version__
|
||||
from vibe.acp.acp_logger import acp_message_observer
|
||||
from vibe.acp.commands import AcpCommandRegistry
|
||||
from vibe.acp.exceptions import (
|
||||
ConfigurationError,
|
||||
ConversationLimitError,
|
||||
|
|
@ -109,6 +113,7 @@ from vibe.core.proxy_setup import (
|
|||
unset_proxy_var,
|
||||
)
|
||||
from vibe.core.session.session_loader import SessionLoader
|
||||
from vibe.core.skills.manager import SkillManager
|
||||
from vibe.core.tools.permissions import RequiredPermission
|
||||
from vibe.core.types import (
|
||||
AgentProfileChangedEvent,
|
||||
|
|
@ -143,6 +148,7 @@ class AcpSessionLoop(BaseModel):
|
|||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
id: str
|
||||
agent_loop: AgentLoop
|
||||
command_registry: SkipValidation[AcpCommandRegistry]
|
||||
task: asyncio.Task[None] | None = None
|
||||
|
||||
|
||||
|
|
@ -254,9 +260,16 @@ class VibeAcpAgentLoop(AcpAgent):
|
|||
async def _create_acp_session(
|
||||
self, session_id: str, agent_loop: AgentLoop
|
||||
) -> AcpSessionLoop:
|
||||
session = AcpSessionLoop(id=session_id, agent_loop=agent_loop)
|
||||
command_registry = AcpCommandRegistry()
|
||||
session = AcpSessionLoop(
|
||||
id=session_id, agent_loop=agent_loop, command_registry=command_registry
|
||||
)
|
||||
self.sessions[session.id] = session
|
||||
|
||||
command_registry.set_on_changed(
|
||||
lambda: self._send_available_commands(session.id)
|
||||
)
|
||||
|
||||
if not agent_loop.auto_approve:
|
||||
agent_loop.set_approval_callback(self._create_approval_callback(session.id))
|
||||
|
||||
|
|
@ -463,137 +476,41 @@ class VibeAcpAgentLoop(AcpAgent):
|
|||
)
|
||||
|
||||
async def _send_available_commands(self, session_id: str) -> None:
|
||||
commands = [
|
||||
AvailableCommand(
|
||||
name="proxy-setup",
|
||||
description="Configure proxy and SSL certificate settings",
|
||||
input=AvailableCommandInput(
|
||||
root=UnstructuredCommandInput(
|
||||
hint="KEY value to set, KEY to unset, or empty for help"
|
||||
)
|
||||
),
|
||||
),
|
||||
AvailableCommand(
|
||||
name="leanstall",
|
||||
description="Install the Lean 4 agent (leanstral)",
|
||||
input=AvailableCommandInput(root=UnstructuredCommandInput(hint="")),
|
||||
),
|
||||
AvailableCommand(
|
||||
name="data-retention",
|
||||
description="Show data retention information",
|
||||
input=AvailableCommandInput(root=UnstructuredCommandInput(hint="")),
|
||||
),
|
||||
AvailableCommand(
|
||||
name="unleanstall",
|
||||
description="Uninstall the Lean 4 agent",
|
||||
input=AvailableCommandInput(root=UnstructuredCommandInput(hint="")),
|
||||
),
|
||||
]
|
||||
|
||||
update = update_available_commands(commands)
|
||||
await self.client.session_update(session_id=session_id, update=update)
|
||||
|
||||
async def _handle_proxy_setup_command(
|
||||
self, session_id: str, text_prompt: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
args = text_prompt.strip()[len("/proxy-setup") :].strip()
|
||||
|
||||
try:
|
||||
if not args:
|
||||
message = get_proxy_help_text()
|
||||
else:
|
||||
key, value = parse_proxy_command(args)
|
||||
if value is not None:
|
||||
set_proxy_var(key, value)
|
||||
message = f"Set `{key}={value}` in ~/.vibe/.env\n\nPlease start a new chat for changes to take effect."
|
||||
else:
|
||||
unset_proxy_var(key)
|
||||
message = f"Removed `{key}` from ~/.vibe/.env\n\nPlease start a new chat for changes to take effect."
|
||||
except ProxySetupError as e:
|
||||
message = f"Error: {e}"
|
||||
|
||||
await self.client.session_update(
|
||||
session_id=session_id,
|
||||
update=AgentMessageChunk(
|
||||
session_update="agent_message_chunk",
|
||||
content=TextContentBlock(type="text", text=message),
|
||||
message_id=str(uuid4()),
|
||||
),
|
||||
)
|
||||
return PromptResponse(stop_reason="end_turn", user_message_id=message_id)
|
||||
|
||||
async def _handle_leanstall_command(
|
||||
self, session_id: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
session = self._get_session(session_id)
|
||||
current = list(session.agent_loop.base_config.installed_agents)
|
||||
if "lean" in current:
|
||||
message = "Lean agent is already installed."
|
||||
else:
|
||||
VibeConfig.save_updates({"installed_agents": [*current, "lean"]})
|
||||
new_config = VibeConfig.load(
|
||||
tool_paths=session.agent_loop.config.tool_paths,
|
||||
disabled_tools=["ask_user_question"],
|
||||
|
||||
commands: list[AvailableCommand] = []
|
||||
|
||||
for cmd in session.command_registry.commands.values():
|
||||
input_spec = (
|
||||
AvailableCommandInput(
|
||||
root=UnstructuredCommandInput(hint=cmd.input_hint)
|
||||
)
|
||||
if cmd.input_hint
|
||||
else None
|
||||
)
|
||||
await session.agent_loop.reload_with_initial_messages(
|
||||
base_config=new_config
|
||||
commands.append(
|
||||
AvailableCommand(
|
||||
name=cmd.name, description=cmd.description, input=input_spec
|
||||
)
|
||||
)
|
||||
message = (
|
||||
"Lean agent installed. Start a new session to switch to Lean mode."
|
||||
|
||||
builtin_names = set(session.command_registry.commands)
|
||||
for skill in session.agent_loop.skill_manager.available_skills.values():
|
||||
if not skill.user_invocable or skill.name in builtin_names:
|
||||
continue
|
||||
commands.append(
|
||||
AvailableCommand(
|
||||
name=skill.name,
|
||||
description=skill.description,
|
||||
input=AvailableCommandInput(
|
||||
root=UnstructuredCommandInput(hint="instructions for the skill")
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
await self.client.session_update(
|
||||
session_id=session_id,
|
||||
update=AgentMessageChunk(
|
||||
session_update="agent_message_chunk",
|
||||
content=TextContentBlock(type="text", text=message),
|
||||
message_id=str(uuid4()),
|
||||
),
|
||||
session_id=session_id, update=update_available_commands(commands)
|
||||
)
|
||||
return PromptResponse(stop_reason="end_turn", user_message_id=message_id)
|
||||
|
||||
async def _handle_data_retention_command(
|
||||
self, session_id: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
await self.client.session_update(
|
||||
session_id=session_id,
|
||||
update=AgentMessageChunk(
|
||||
session_update="agent_message_chunk",
|
||||
content=TextContentBlock(type="text", text=DATA_RETENTION_MESSAGE),
|
||||
message_id=str(uuid4()),
|
||||
),
|
||||
)
|
||||
return PromptResponse(stop_reason="end_turn", user_message_id=message_id)
|
||||
|
||||
async def _handle_unleanstall_command(
|
||||
self, session_id: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
session = self._get_session(session_id)
|
||||
current = list(session.agent_loop.base_config.installed_agents)
|
||||
if "lean" not in current:
|
||||
message = "Lean agent is not installed."
|
||||
else:
|
||||
VibeConfig.save_updates({
|
||||
"installed_agents": [a for a in current if a != "lean"]
|
||||
})
|
||||
new_config = VibeConfig.load(
|
||||
tool_paths=session.agent_loop.config.tool_paths,
|
||||
disabled_tools=["ask_user_question"],
|
||||
)
|
||||
await session.agent_loop.reload_with_initial_messages(
|
||||
base_config=new_config
|
||||
)
|
||||
message = "Lean agent uninstalled."
|
||||
|
||||
await self.client.session_update(
|
||||
session_id=session_id,
|
||||
update=AgentMessageChunk(
|
||||
session_update="agent_message_chunk",
|
||||
content=TextContentBlock(type="text", text=message),
|
||||
message_id=str(uuid4()),
|
||||
),
|
||||
)
|
||||
return PromptResponse(stop_reason="end_turn", user_message_id=message_id)
|
||||
|
||||
@override
|
||||
async def load_session(
|
||||
|
|
@ -723,16 +640,8 @@ class VibeAcpAgentLoop(AcpAgent):
|
|||
if not success:
|
||||
return None
|
||||
|
||||
profiles = list(session.agent_loop.agent_manager.available_agents.values())
|
||||
_, modes_config = make_mode_response(
|
||||
profiles, session.agent_loop.agent_profile.name
|
||||
)
|
||||
_, models_config = make_model_response(
|
||||
session.agent_loop.config.models, session.agent_loop.config.active_model
|
||||
)
|
||||
|
||||
return SetSessionConfigOptionResponse(
|
||||
config_options=[modes_config, models_config]
|
||||
config_options=self._build_config_options(session)
|
||||
)
|
||||
|
||||
@override
|
||||
|
|
@ -779,23 +688,21 @@ class VibeAcpAgentLoop(AcpAgent):
|
|||
text_prompt = self._build_text_prompt(prompt)
|
||||
resolved_message_id = _resolved_user_message_id(message_id)
|
||||
|
||||
if text_prompt.strip().lower().startswith("/proxy-setup"):
|
||||
return await self._handle_proxy_setup_command(
|
||||
session_id, text_prompt, resolved_message_id
|
||||
)
|
||||
if command_response := await self._maybe_handle_builtin_command(
|
||||
session, text_prompt, resolved_message_id
|
||||
):
|
||||
return command_response
|
||||
|
||||
if text_prompt.strip().lower().startswith("/unleanstall"):
|
||||
return await self._handle_unleanstall_command(
|
||||
session_id, resolved_message_id
|
||||
)
|
||||
try:
|
||||
skill = session.agent_loop.skill_manager.parse_skill_command(text_prompt)
|
||||
except OSError as e:
|
||||
raise InternalError(f"Failed to read skill file: {e}") from e
|
||||
|
||||
if text_prompt.strip().lower().startswith("/leanstall"):
|
||||
return await self._handle_leanstall_command(session_id, resolved_message_id)
|
||||
|
||||
if text_prompt.strip().lower().startswith("/data-retention"):
|
||||
return await self._handle_data_retention_command(
|
||||
session_id, resolved_message_id
|
||||
if skill:
|
||||
session.agent_loop.telemetry_client.send_slash_command_used(
|
||||
skill.name, "skill"
|
||||
)
|
||||
text_prompt = SkillManager.build_skill_prompt(text_prompt, skill)
|
||||
|
||||
async def agent_loop_task() -> None:
|
||||
async for update in self._run_agent_loop(
|
||||
|
|
@ -881,6 +788,22 @@ class VibeAcpAgentLoop(AcpAgent):
|
|||
)
|
||||
return text_prompt
|
||||
|
||||
async def _maybe_handle_builtin_command(
|
||||
self, session: AcpSessionLoop, text_prompt: str, message_id: str
|
||||
) -> PromptResponse | None:
|
||||
normalized = text_prompt.strip().lower()
|
||||
parts = normalized.split(None, 1)
|
||||
if not parts or not parts[0].startswith("/"):
|
||||
return None
|
||||
|
||||
cmd_name = parts[0][1:] # strip leading "/"
|
||||
command = session.command_registry.get(cmd_name)
|
||||
if command is None:
|
||||
return None
|
||||
|
||||
handler = getattr(self, command.handler)
|
||||
return await handler(session, text_prompt, message_id)
|
||||
|
||||
async def _run_agent_loop(
|
||||
self, session: AcpSessionLoop, prompt: str, client_message_id: str | None = None
|
||||
) -> AsyncGenerator[SessionUpdate]:
|
||||
|
|
@ -990,6 +913,218 @@ class VibeAcpAgentLoop(AcpAgent):
|
|||
def on_connect(self, conn: Client) -> None:
|
||||
self.client = conn
|
||||
|
||||
# -- Command handlers ------------------------------------------------------
|
||||
|
||||
async def _command_reply(
|
||||
self, session: AcpSessionLoop, text: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
"""Send a text message to the client and return an end-turn response."""
|
||||
await self.client.session_update(
|
||||
session_id=session.id,
|
||||
update=AgentMessageChunk(
|
||||
session_update="agent_message_chunk",
|
||||
content=TextContentBlock(type="text", text=text),
|
||||
message_id=str(uuid4()),
|
||||
),
|
||||
)
|
||||
return PromptResponse(stop_reason="end_turn", user_message_id=message_id)
|
||||
|
||||
async def _handle_help(
|
||||
self, session: AcpSessionLoop, text_prompt: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
lines = ["### Available Commands", ""]
|
||||
for cmd in session.command_registry.commands.values():
|
||||
hint = f" `<{cmd.input_hint}>`" if cmd.input_hint else ""
|
||||
lines.append(f"- `/{cmd.name}`{hint}: {cmd.description}")
|
||||
|
||||
builtin_names = set(session.command_registry.commands)
|
||||
invocable = {
|
||||
n: s
|
||||
for n, s in session.agent_loop.skill_manager.available_skills.items()
|
||||
if s.user_invocable and n not in builtin_names
|
||||
}
|
||||
if invocable:
|
||||
lines.extend(["", "### Available Skills", ""])
|
||||
for name, info in invocable.items():
|
||||
lines.append(f"- `/{name}`: {info.description}")
|
||||
|
||||
return await self._command_reply(session, "\n".join(lines), message_id)
|
||||
|
||||
async def _handle_compact(
|
||||
self, session: AcpSessionLoop, text_prompt: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
if len(session.agent_loop.messages) <= 1:
|
||||
return await self._command_reply(
|
||||
session, "No conversation history to compact yet.", message_id
|
||||
)
|
||||
|
||||
tool_call_id = str(uuid4())
|
||||
old_tokens = session.agent_loop.stats.context_tokens
|
||||
|
||||
start_event = CompactStartEvent(
|
||||
current_context_tokens=old_tokens or 0,
|
||||
threshold=0,
|
||||
tool_call_id=tool_call_id,
|
||||
)
|
||||
await self.client.session_update(
|
||||
session_id=session.id,
|
||||
update=create_compact_start_session_update(start_event),
|
||||
)
|
||||
|
||||
await session.agent_loop.compact()
|
||||
new_tokens = session.agent_loop.stats.context_tokens
|
||||
|
||||
end_event = CompactEndEvent(
|
||||
old_context_tokens=old_tokens or 0,
|
||||
new_context_tokens=new_tokens or 0,
|
||||
summary_length=0,
|
||||
tool_call_id=tool_call_id,
|
||||
)
|
||||
await self.client.session_update(
|
||||
session_id=session.id, update=create_compact_end_session_update(end_event)
|
||||
)
|
||||
|
||||
return PromptResponse(stop_reason="end_turn", user_message_id=message_id)
|
||||
|
||||
async def _reload_session_config(self, session: AcpSessionLoop) -> None:
|
||||
"""Reload config from disk and reinitialize the agent loop."""
|
||||
new_config = VibeConfig.load(
|
||||
tool_paths=session.agent_loop.config.tool_paths,
|
||||
disabled_tools=["ask_user_question"],
|
||||
)
|
||||
await session.agent_loop.reload_with_initial_messages(base_config=new_config)
|
||||
|
||||
async def _handle_reload(
|
||||
self, session: AcpSessionLoop, text_prompt: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
try:
|
||||
await self._reload_session_config(session)
|
||||
except Exception as e:
|
||||
return await self._command_reply(
|
||||
session, f"Failed to reload config: {e}", message_id
|
||||
)
|
||||
|
||||
try:
|
||||
await session.command_registry.notify_changed()
|
||||
except Exception as e:
|
||||
return await self._command_reply(
|
||||
session,
|
||||
f"Configuration reloaded, but failed to advertise updated commands: {e}",
|
||||
message_id,
|
||||
)
|
||||
|
||||
return await self._command_reply(
|
||||
session,
|
||||
"Configuration reloaded (includes agent instructions and skills).",
|
||||
message_id,
|
||||
)
|
||||
|
||||
async def _handle_log(
|
||||
self, session: AcpSessionLoop, text_prompt: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
logger = session.agent_loop.session_logger
|
||||
if not logger.enabled:
|
||||
return await self._command_reply(
|
||||
session, "Session logging is disabled in configuration.", message_id
|
||||
)
|
||||
|
||||
return await self._command_reply(
|
||||
session,
|
||||
f"## Current Log Directory\n\n`{logger.session_dir}`\n\n"
|
||||
"You can send this directory to share your interaction.",
|
||||
message_id,
|
||||
)
|
||||
|
||||
async def _handle_proxy_setup(
|
||||
self, session: AcpSessionLoop, text_prompt: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
parts = text_prompt.strip().split(None, 1)
|
||||
args = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
try:
|
||||
if not args:
|
||||
message = get_proxy_help_text()
|
||||
else:
|
||||
key, value = parse_proxy_command(args)
|
||||
if value is not None:
|
||||
set_proxy_var(key, value)
|
||||
message = (
|
||||
f"Set `{key}={value}` in ~/.vibe/.env\n\n"
|
||||
"Please start a new chat for changes to take effect."
|
||||
)
|
||||
else:
|
||||
unset_proxy_var(key)
|
||||
message = (
|
||||
f"Removed `{key}` from ~/.vibe/.env\n\n"
|
||||
"Please start a new chat for changes to take effect."
|
||||
)
|
||||
except ProxySetupError as e:
|
||||
message = f"Error: {e}"
|
||||
|
||||
return await self._command_reply(session, message, message_id)
|
||||
|
||||
def _build_config_options(
|
||||
self, session: AcpSessionLoop
|
||||
) -> list[SessionConfigOptionSelect | SessionConfigOptionBoolean]:
|
||||
"""Build the current modes + models config options for a session."""
|
||||
profiles = list(session.agent_loop.agent_manager.available_agents.values())
|
||||
_, modes_config = make_mode_response(
|
||||
profiles, session.agent_loop.agent_profile.name
|
||||
)
|
||||
_, models_config = make_model_response(
|
||||
session.agent_loop.config.models, session.agent_loop.config.active_model
|
||||
)
|
||||
return [modes_config, models_config]
|
||||
|
||||
async def _send_config_option_update(self, session: AcpSessionLoop) -> None:
|
||||
"""Push updated config options (modes, models) to the client."""
|
||||
await self.client.session_update(
|
||||
session_id=session.id,
|
||||
update=ConfigOptionUpdate(
|
||||
session_update="config_option_update",
|
||||
config_options=self._build_config_options(session),
|
||||
),
|
||||
)
|
||||
|
||||
async def _handle_leanstall(
|
||||
self, session: AcpSessionLoop, text_prompt: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
current = list(session.agent_loop.base_config.installed_agents)
|
||||
if "lean" in current:
|
||||
return await self._command_reply(
|
||||
session, "Lean agent is already installed.", message_id
|
||||
)
|
||||
|
||||
VibeConfig.save_updates({"installed_agents": [*current, "lean"]})
|
||||
await self._reload_session_config(session)
|
||||
await self._send_config_option_update(session)
|
||||
return await self._command_reply(
|
||||
session,
|
||||
"Lean agent installed. Start a new session to switch to Lean mode.",
|
||||
message_id,
|
||||
)
|
||||
|
||||
async def _handle_unleanstall(
|
||||
self, session: AcpSessionLoop, text_prompt: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
current = list(session.agent_loop.base_config.installed_agents)
|
||||
if "lean" not in current:
|
||||
return await self._command_reply(
|
||||
session, "Lean agent is not installed.", message_id
|
||||
)
|
||||
|
||||
VibeConfig.save_updates({
|
||||
"installed_agents": [a for a in current if a != "lean"]
|
||||
})
|
||||
await self._reload_session_config(session)
|
||||
await self._send_config_option_update(session)
|
||||
return await self._command_reply(session, "Lean agent uninstalled.", message_id)
|
||||
|
||||
async def _handle_data_retention(
|
||||
self, session: AcpSessionLoop, text_prompt: str, message_id: str
|
||||
) -> PromptResponse:
|
||||
return await self._command_reply(session, DATA_RETENTION_MESSAGE, message_id)
|
||||
|
||||
|
||||
def run_acp_server() -> None:
|
||||
try:
|
||||
|
|
|
|||
5
vibe/acp/commands/__init__.py
Normal file
5
vibe/acp/commands/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from vibe.acp.commands.registry import AcpCommandRegistry
|
||||
|
||||
__all__ = ["AcpCommandRegistry"]
|
||||
88
vibe/acp/commands/registry.py
Normal file
88
vibe/acp/commands/registry.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
type OnCommandsChanged = Callable[[], Awaitable[None]]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AcpCommand:
|
||||
"""Command advertised to ACP clients via available_commands_update."""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
handler: str
|
||||
input_hint: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class AcpCommandRegistry:
|
||||
"""Registry of ACP commands. Notifies listeners when commands change."""
|
||||
|
||||
_commands: dict[str, AcpCommand] = field(default_factory=dict)
|
||||
_on_changed: OnCommandsChanged | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not self._commands:
|
||||
self._commands = _build_commands()
|
||||
|
||||
def set_on_changed(self, callback: OnCommandsChanged) -> None:
|
||||
self._on_changed = callback
|
||||
|
||||
@property
|
||||
def commands(self) -> dict[str, AcpCommand]:
|
||||
return self._commands
|
||||
|
||||
def get(self, name: str) -> AcpCommand | None:
|
||||
return self._commands.get(name)
|
||||
|
||||
async def notify_changed(self) -> None:
|
||||
if self._on_changed is not None:
|
||||
await self._on_changed()
|
||||
|
||||
|
||||
def _build_commands() -> dict[str, AcpCommand]:
|
||||
return {
|
||||
"help": AcpCommand(
|
||||
name="help",
|
||||
description="Show available commands and keyboard shortcuts",
|
||||
handler="_handle_help",
|
||||
),
|
||||
"compact": AcpCommand(
|
||||
name="compact",
|
||||
description="Compact conversation history by summarizing",
|
||||
handler="_handle_compact",
|
||||
),
|
||||
"reload": AcpCommand(
|
||||
name="reload",
|
||||
description="Reload configuration, agent instructions, and skills from disk",
|
||||
handler="_handle_reload",
|
||||
),
|
||||
"log": AcpCommand(
|
||||
name="log",
|
||||
description="Show path to current session log directory",
|
||||
handler="_handle_log",
|
||||
),
|
||||
"proxy-setup": AcpCommand(
|
||||
name="proxy-setup",
|
||||
description="Configure proxy and SSL certificate settings",
|
||||
handler="_handle_proxy_setup",
|
||||
input_hint="KEY value to set, KEY to unset, or empty for help",
|
||||
),
|
||||
"leanstall": AcpCommand(
|
||||
name="leanstall",
|
||||
description="Install the Lean 4 agent (leanstral)",
|
||||
handler="_handle_leanstall",
|
||||
),
|
||||
"unleanstall": AcpCommand(
|
||||
name="unleanstall",
|
||||
description="Uninstall the Lean 4 agent",
|
||||
handler="_handle_unleanstall",
|
||||
),
|
||||
"data-retention": AcpCommand(
|
||||
name="data-retention",
|
||||
description="Show data retention information",
|
||||
handler="_handle_data_retention",
|
||||
),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue