agentpool 2.1.9__py3-none-any.whl → 2.2.3__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.
- acp/__init__.py +13 -0
- acp/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -0
- acp/notifications.py +2 -1
- acp/stdio.py +39 -9
- acp/transports.py +362 -2
- acp/utils.py +15 -2
- agentpool/__init__.py +4 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +203 -88
- agentpool/agents/acp_agent/acp_converters.py +46 -21
- agentpool/agents/acp_agent/client_handler.py +157 -3
- agentpool/agents/acp_agent/session_state.py +4 -1
- agentpool/agents/agent.py +314 -107
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +90 -21
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/base_agent.py +163 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +626 -179
- agentpool/agents/claude_code_agent/converters.py +71 -3
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +2 -0
- agentpool/agents/events/builtin_handlers.py +2 -1
- agentpool/agents/events/event_emitter.py +29 -2
- agentpool/agents/events/events.py +20 -0
- agentpool/agents/modes.py +54 -0
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/common_types.py +21 -0
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/claude_code_agent.yml +3 -0
- agentpool/delegation/pool.py +37 -29
- agentpool/delegation/team.py +1 -0
- agentpool/delegation/teamrun.py +1 -0
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +12 -3
- agentpool/mcp_server/manager.py +25 -31
- agentpool/mcp_server/registries/official_registry_client.py +25 -0
- agentpool/mcp_server/tool_bridge.py +78 -66
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/message_history.py +12 -0
- agentpool/messaging/messages.py +52 -9
- agentpool/messaging/processing.py +3 -1
- agentpool/models/acp_agents/base.py +0 -22
- agentpool/models/acp_agents/mcp_capable.py +8 -148
- agentpool/models/acp_agents/non_mcp.py +129 -72
- agentpool/models/agents.py +35 -13
- agentpool/models/claude_code_agents.py +33 -2
- agentpool/models/manifest.py +43 -0
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +9 -1
- agentpool/resource_providers/aggregating.py +52 -3
- agentpool/resource_providers/base.py +57 -1
- agentpool/resource_providers/mcp_provider.py +23 -0
- agentpool/resource_providers/plan_provider.py +130 -41
- agentpool/resource_providers/pool.py +2 -0
- agentpool/resource_providers/static.py +2 -0
- agentpool/sessions/__init__.py +2 -1
- agentpool/sessions/manager.py +31 -2
- agentpool/sessions/models.py +50 -0
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +217 -1
- agentpool/testing.py +537 -19
- agentpool/utils/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +690 -1
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/METADATA +27 -7
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/RECORD +170 -112
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +4 -0
- agentpool_cli/serve_acp.py +41 -20
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_commands/__init__.py +30 -0
- agentpool_commands/agents.py +74 -1
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +51 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/mcp_server.py +131 -1
- agentpool_config/storage.py +46 -1
- agentpool_config/tools.py +7 -1
- agentpool_config/toolsets.py +92 -148
- agentpool_server/acp_server/acp_agent.py +134 -150
- agentpool_server/acp_server/commands/acp_commands.py +216 -51
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +10 -10
- agentpool_server/acp_server/server.py +23 -79
- agentpool_server/acp_server/session.py +181 -19
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +362 -0
- agentpool_server/opencode_server/__init__.py +27 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +869 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +269 -0
- agentpool_server/opencode_server/models/__init__.py +228 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +60 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +647 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +25 -0
- agentpool_server/opencode_server/models/message.py +162 -0
- agentpool_server/opencode_server/models/parts.py +190 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/session.py +99 -0
- agentpool_server/opencode_server/routes/__init__.py +25 -0
- agentpool_server/opencode_server/routes/agent_routes.py +442 -0
- agentpool_server/opencode_server/routes/app_routes.py +139 -0
- agentpool_server/opencode_server/routes/config_routes.py +241 -0
- agentpool_server/opencode_server/routes/file_routes.py +392 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +705 -0
- agentpool_server/opencode_server/routes/pty_routes.py +299 -0
- agentpool_server/opencode_server/routes/session_routes.py +1205 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +430 -0
- agentpool_server/opencode_server/state.py +121 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +16 -0
- agentpool_storage/base.py +103 -0
- agentpool_storage/claude_provider.py +907 -0
- agentpool_storage/file_provider.py +129 -0
- agentpool_storage/memory_provider.py +61 -0
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider.py +730 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +6 -0
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +134 -1
- agentpool_storage/sql_provider/utils.py +10 -1
- agentpool_storage/text_log_provider.py +1 -0
- agentpool_toolsets/builtin/__init__.py +0 -8
- agentpool_toolsets/builtin/code.py +95 -56
- agentpool_toolsets/builtin/debug.py +16 -21
- agentpool_toolsets/builtin/execution_environment.py +99 -103
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +86 -4
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +74 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +159 -38
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +454 -0
- agentpool_toolsets/mcp_run_toolset.py +84 -6
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/entry_points.txt +0 -0
- {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Global routes (health, events)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from fastapi import APIRouter
|
|
10
|
+
from sse_starlette.sse import EventSourceResponse
|
|
11
|
+
|
|
12
|
+
from agentpool_server.opencode_server.dependencies import StateDep # noqa: TC001
|
|
13
|
+
from agentpool_server.opencode_server.models import ( # noqa: TC001
|
|
14
|
+
Event,
|
|
15
|
+
HealthResponse,
|
|
16
|
+
ServerConnectedEvent,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import AsyncGenerator
|
|
22
|
+
|
|
23
|
+
from agentpool_server.opencode_server.state import ServerState
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
router = APIRouter(tags=["global"])
|
|
28
|
+
|
|
29
|
+
VERSION = "0.1.0"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.get("/global/health")
|
|
33
|
+
async def get_health() -> HealthResponse:
|
|
34
|
+
"""Get server health status."""
|
|
35
|
+
return HealthResponse(healthy=True, version=VERSION)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _serialize_event(event: Event, wrap_payload: bool = False) -> str:
|
|
39
|
+
"""Serialize event, optionally wrapping in payload structure."""
|
|
40
|
+
import json
|
|
41
|
+
|
|
42
|
+
event_data = event.model_dump(by_alias=True, exclude_none=True)
|
|
43
|
+
if wrap_payload:
|
|
44
|
+
return json.dumps({"payload": event_data})
|
|
45
|
+
return json.dumps(event_data)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def _event_generator(
|
|
49
|
+
state: ServerState, *, wrap_payload: bool = False
|
|
50
|
+
) -> AsyncGenerator[dict[str, Any]]:
|
|
51
|
+
"""Generate SSE events."""
|
|
52
|
+
queue: asyncio.Queue[Event] = asyncio.Queue()
|
|
53
|
+
state.event_subscribers.append(queue)
|
|
54
|
+
subscriber_count = len(state.event_subscribers)
|
|
55
|
+
logger.info("SSE: New client connected (total subscribers: %s)", subscriber_count)
|
|
56
|
+
|
|
57
|
+
# Trigger first subscriber callback if this is the first connection
|
|
58
|
+
if (
|
|
59
|
+
subscriber_count == 1
|
|
60
|
+
and not state._first_subscriber_triggered
|
|
61
|
+
and state.on_first_subscriber is not None
|
|
62
|
+
):
|
|
63
|
+
state._first_subscriber_triggered = True
|
|
64
|
+
state.create_background_task(state.on_first_subscriber(), name="on_first_subscriber")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# Send initial connected event
|
|
68
|
+
connected = ServerConnectedEvent()
|
|
69
|
+
data = _serialize_event(connected, wrap_payload=wrap_payload)
|
|
70
|
+
logger.info("SSE: Sending connected event: %s", data)
|
|
71
|
+
yield {"data": data}
|
|
72
|
+
# Stream events
|
|
73
|
+
while True:
|
|
74
|
+
event = await queue.get()
|
|
75
|
+
data = _serialize_event(event, wrap_payload=wrap_payload)
|
|
76
|
+
logger.info("SSE: Sending event: %s", event.type)
|
|
77
|
+
yield {"data": data}
|
|
78
|
+
finally:
|
|
79
|
+
state.event_subscribers.remove(queue)
|
|
80
|
+
logger.info(
|
|
81
|
+
"SSE: Client disconnected (remaining subscribers: %s)", len(state.event_subscribers)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@router.get("/global/event")
|
|
86
|
+
async def get_global_events(state: StateDep) -> EventSourceResponse:
|
|
87
|
+
"""Get global events as SSE stream (uses payload wrapper)."""
|
|
88
|
+
return EventSourceResponse(_event_generator(state, wrap_payload=True), sep="\n")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@router.get("/event")
|
|
92
|
+
async def get_events(state: StateDep) -> EventSourceResponse:
|
|
93
|
+
"""Get events as SSE stream (no payload wrapper)."""
|
|
94
|
+
return EventSourceResponse(_event_generator(state, wrap_payload=False), sep="\n")
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""LSP (Language Server Protocol) routes.
|
|
2
|
+
|
|
3
|
+
Provides endpoints for LSP server status and diagnostics,
|
|
4
|
+
compatible with OpenCode's LSP API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from contextlib import suppress
|
|
10
|
+
import os
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
|
|
16
|
+
from agentpool_server.opencode_server.dependencies import StateDep # noqa: TC001
|
|
17
|
+
from agentpool_server.opencode_server.models.events import LspStatus, LspUpdatedEvent
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# Diagnostic Models (matching OpenCode's LSP diagnostic format)
|
|
22
|
+
# =============================================================================
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DiagnosticPosition(BaseModel):
|
|
26
|
+
"""Position in a text document."""
|
|
27
|
+
|
|
28
|
+
line: int
|
|
29
|
+
character: int
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DiagnosticRange(BaseModel):
|
|
33
|
+
"""Range in a text document."""
|
|
34
|
+
|
|
35
|
+
start: DiagnosticPosition
|
|
36
|
+
end: DiagnosticPosition
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Diagnostic(BaseModel):
|
|
40
|
+
"""LSP Diagnostic matching vscode-languageserver-types format."""
|
|
41
|
+
|
|
42
|
+
range: DiagnosticRange
|
|
43
|
+
message: str
|
|
44
|
+
severity: int | None = None # 1=Error, 2=Warning, 3=Info, 4=Hint
|
|
45
|
+
code: str | int | None = None
|
|
46
|
+
source: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class FormatterStatus(BaseModel):
|
|
50
|
+
"""Formatter status information."""
|
|
51
|
+
|
|
52
|
+
id: str
|
|
53
|
+
"""Formatter identifier."""
|
|
54
|
+
|
|
55
|
+
name: str
|
|
56
|
+
"""Formatter name."""
|
|
57
|
+
|
|
58
|
+
root: str
|
|
59
|
+
"""Workspace root path."""
|
|
60
|
+
|
|
61
|
+
status: Literal["connected", "error"]
|
|
62
|
+
"""Connection status."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
router = APIRouter(tags=["lsp"])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@router.get("/lsp")
|
|
69
|
+
async def list_lsp_servers(state: StateDep) -> list[LspStatus]:
|
|
70
|
+
"""List all active LSP servers.
|
|
71
|
+
|
|
72
|
+
Returns the status of all running LSP servers, including their
|
|
73
|
+
connection state and workspace root.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
List of LSP server status objects.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
lsp_manager = state.get_or_create_lsp_manager()
|
|
80
|
+
except RuntimeError:
|
|
81
|
+
# Agent doesn't have an execution environment - return empty list
|
|
82
|
+
return []
|
|
83
|
+
|
|
84
|
+
servers: list[LspStatus] = []
|
|
85
|
+
for server_id, server_state in lsp_manager._servers.items():
|
|
86
|
+
# Get relative root path
|
|
87
|
+
root_uri = server_state.root_uri or ""
|
|
88
|
+
if root_uri.startswith("file://"):
|
|
89
|
+
root_path = root_uri[7:] # Remove file:// prefix
|
|
90
|
+
# Make path relative to working directory
|
|
91
|
+
with suppress(ValueError):
|
|
92
|
+
root_path = os.path.relpath(root_path, state.working_dir)
|
|
93
|
+
else:
|
|
94
|
+
root_path = root_uri
|
|
95
|
+
|
|
96
|
+
servers.append(
|
|
97
|
+
LspStatus(
|
|
98
|
+
id=server_id,
|
|
99
|
+
name=server_id,
|
|
100
|
+
root=root_path,
|
|
101
|
+
status="connected" if server_state.initialized else "error",
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return servers
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@router.post("/lsp/start")
|
|
109
|
+
async def start_lsp_server(
|
|
110
|
+
state: StateDep,
|
|
111
|
+
server_id: str = Query(..., description="LSP server ID (e.g., 'pyright', 'rust-analyzer')"),
|
|
112
|
+
root_uri: str | None = Query(None, description="Workspace root URI"),
|
|
113
|
+
) -> LspStatus:
|
|
114
|
+
"""Start an LSP server.
|
|
115
|
+
|
|
116
|
+
Starts the specified LSP server for the given workspace root.
|
|
117
|
+
If no root_uri is provided, uses the server's working directory.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
state: Server state dependency (injected).
|
|
121
|
+
server_id: The LSP server identifier (e.g., 'pyright', 'typescript').
|
|
122
|
+
root_uri: Optional workspace root URI (file:// format).
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
The started server's status.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
HTTPException: If the server fails to start or is not registered.
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
lsp_manager = state.get_or_create_lsp_manager()
|
|
132
|
+
except RuntimeError as e:
|
|
133
|
+
raise HTTPException(status_code=503, detail=str(e)) from e
|
|
134
|
+
|
|
135
|
+
# Default to working directory if no root provided
|
|
136
|
+
if root_uri is None:
|
|
137
|
+
root_uri = f"file://{state.working_dir}"
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
server_state = await lsp_manager.start_server(server_id, root_uri)
|
|
141
|
+
except ValueError as e:
|
|
142
|
+
raise HTTPException(status_code=404, detail=str(e)) from e
|
|
143
|
+
except RuntimeError as e:
|
|
144
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
145
|
+
|
|
146
|
+
# Emit lsp.updated event to notify clients of server status change
|
|
147
|
+
await state.broadcast_event(LspUpdatedEvent.create())
|
|
148
|
+
|
|
149
|
+
# Get relative root path for response
|
|
150
|
+
root_path = root_uri
|
|
151
|
+
if root_uri.startswith("file://"):
|
|
152
|
+
root_path = root_uri[7:]
|
|
153
|
+
with suppress(ValueError):
|
|
154
|
+
root_path = os.path.relpath(root_path, state.working_dir)
|
|
155
|
+
|
|
156
|
+
return LspStatus(
|
|
157
|
+
id=server_id,
|
|
158
|
+
name=server_id,
|
|
159
|
+
root=root_path,
|
|
160
|
+
status="connected" if server_state.initialized else "error",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@router.post("/lsp/stop")
|
|
165
|
+
async def stop_lsp_server(
|
|
166
|
+
state: StateDep,
|
|
167
|
+
server_id: str = Query(..., description="LSP server ID to stop"),
|
|
168
|
+
) -> dict[str, str]:
|
|
169
|
+
"""Stop an LSP server.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
state: Server state dependency (injected).
|
|
173
|
+
server_id: The LSP server identifier to stop.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Success message.
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
lsp_manager = state.get_or_create_lsp_manager()
|
|
180
|
+
except RuntimeError:
|
|
181
|
+
return {"status": "ok", "message": "No LSP manager active"}
|
|
182
|
+
|
|
183
|
+
await lsp_manager.stop_server(server_id)
|
|
184
|
+
|
|
185
|
+
# Emit lsp.updated event to notify clients of server status change
|
|
186
|
+
await state.broadcast_event(LspUpdatedEvent.create())
|
|
187
|
+
|
|
188
|
+
return {"status": "ok", "message": f"Server {server_id} stopped"}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@router.get("/lsp/diagnostics")
|
|
192
|
+
async def get_diagnostics(
|
|
193
|
+
state: StateDep,
|
|
194
|
+
path: str | None = Query(None, description="File path to get diagnostics for"),
|
|
195
|
+
) -> dict[str, list[Diagnostic]]:
|
|
196
|
+
"""Get diagnostics from all active LSP servers.
|
|
197
|
+
|
|
198
|
+
Returns diagnostics organized by file path. If a specific path is provided,
|
|
199
|
+
returns diagnostics only for that file using CLI diagnostics.
|
|
200
|
+
|
|
201
|
+
This uses CLI-based diagnostic tools (pyright, mypy, etc.) which are more
|
|
202
|
+
reliable for on-demand checks than the LSP push model.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
state: Server state dependency (injected).
|
|
206
|
+
path: Optional file path to get diagnostics for.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Dictionary mapping file paths to lists of diagnostic objects.
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
lsp_manager = state.get_or_create_lsp_manager()
|
|
213
|
+
except RuntimeError:
|
|
214
|
+
return {}
|
|
215
|
+
|
|
216
|
+
results: dict[str, list[Diagnostic]] = {}
|
|
217
|
+
|
|
218
|
+
# If a specific path is provided, run CLI diagnostics for it
|
|
219
|
+
if path:
|
|
220
|
+
# Make path absolute if needed
|
|
221
|
+
if not os.path.isabs(path): # noqa: PTH117
|
|
222
|
+
path = os.path.join(state.working_dir, path) # noqa: PTH118
|
|
223
|
+
|
|
224
|
+
# Find the appropriate server for this file
|
|
225
|
+
server_info = lsp_manager.get_server_for_file(path)
|
|
226
|
+
if server_info and server_info.has_cli_diagnostics:
|
|
227
|
+
try:
|
|
228
|
+
result = await lsp_manager.run_cli_diagnostics(server_info.id, [path])
|
|
229
|
+
if result.success and result.diagnostics:
|
|
230
|
+
for diag in result.diagnostics:
|
|
231
|
+
file_path = diag.file or path
|
|
232
|
+
if file_path not in results:
|
|
233
|
+
results[file_path] = []
|
|
234
|
+
# Convert from 1-based (CLI tools) to 0-based (LSP)
|
|
235
|
+
results[file_path].append(
|
|
236
|
+
Diagnostic(
|
|
237
|
+
range=DiagnosticRange(
|
|
238
|
+
start=DiagnosticPosition(
|
|
239
|
+
line=max(0, diag.line - 1),
|
|
240
|
+
character=max(0, diag.column - 1),
|
|
241
|
+
),
|
|
242
|
+
end=DiagnosticPosition(
|
|
243
|
+
line=max(0, (diag.end_line or diag.line) - 1),
|
|
244
|
+
character=max(0, (diag.end_column or diag.column) - 1),
|
|
245
|
+
),
|
|
246
|
+
),
|
|
247
|
+
message=diag.message,
|
|
248
|
+
severity=_severity_to_lsp(diag.severity),
|
|
249
|
+
code=diag.code,
|
|
250
|
+
source=diag.source or server_info.id,
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
except Exception: # noqa: BLE001
|
|
254
|
+
# CLI diagnostics failed, return empty
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
return results
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _severity_to_lsp(severity: str) -> int:
|
|
261
|
+
"""Convert severity string to LSP severity number."""
|
|
262
|
+
mapping = {
|
|
263
|
+
"error": 1,
|
|
264
|
+
"warning": 2,
|
|
265
|
+
"info": 3,
|
|
266
|
+
"hint": 4,
|
|
267
|
+
}
|
|
268
|
+
return mapping.get(severity.lower(), 1)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@router.get("/lsp/servers")
|
|
272
|
+
async def list_available_servers(state: StateDep) -> list[dict[str, object]]:
|
|
273
|
+
"""List all registered (available) LSP servers.
|
|
274
|
+
|
|
275
|
+
Returns information about all LSP servers that can be started,
|
|
276
|
+
regardless of whether they are currently running.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
List of server configurations.
|
|
280
|
+
"""
|
|
281
|
+
try:
|
|
282
|
+
lsp_manager = state.get_or_create_lsp_manager()
|
|
283
|
+
except RuntimeError:
|
|
284
|
+
return []
|
|
285
|
+
|
|
286
|
+
servers = []
|
|
287
|
+
for server_id, config in lsp_manager._server_configs.items():
|
|
288
|
+
servers.append({
|
|
289
|
+
"id": server_id,
|
|
290
|
+
"extensions": config.extensions,
|
|
291
|
+
"running": server_id in lsp_manager._servers,
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
return servers
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# =============================================================================
|
|
298
|
+
# Formatter Routes
|
|
299
|
+
# =============================================================================
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@router.get("/formatter")
|
|
303
|
+
async def list_formatters(state: StateDep) -> list[FormatterStatus]:
|
|
304
|
+
"""List all active formatters.
|
|
305
|
+
|
|
306
|
+
Returns the status of all running formatters, including their
|
|
307
|
+
connection state and workspace root.
|
|
308
|
+
|
|
309
|
+
Note: This is currently a stub that returns an empty list.
|
|
310
|
+
Formatter support can be added in the future.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
List of formatter status objects.
|
|
314
|
+
"""
|
|
315
|
+
# Stub implementation - formatters not yet implemented
|
|
316
|
+
# OpenCode has formatters like prettier, biome, etc.
|
|
317
|
+
# For now, return empty list
|
|
318
|
+
_ = state # Reserved for future use
|
|
319
|
+
return []
|