Skip to content

session

code_context_agent.tools.lsp.session

LSP session manager using singleton pattern.

This module provides a session manager that maintains LSP client connections across tool calls. Sessions are expensive to start (subprocess spawn + initialization), so we reuse them.

LspSessionManager

Singleton session manager for LSP connections.

Maintains a pool of LSP client connections keyed by server kind and workspace path. This allows multiple tools to share the same LSP connection efficiently.

Session keys are formatted as "{server_kind}:{workspace_path}".

Example

manager = LspSessionManager() client = await manager.get_or_create("ts", "/path/to/project") symbols = await client.document_symbols("/path/to/file.ts") await manager.shutdown_all()

reset_instance classmethod

reset_instance()

Reset the singleton instance (for testing).

Source code in src/code_context_agent/tools/lsp/session.py
@classmethod
def reset_instance(cls) -> None:
    """Reset the singleton instance (for testing)."""
    if cls._instance is not None:
        cls._instance._sessions = {}
        cls._instance._server_commands = {}
    cls._instance = None

get_server_command_for_session

get_server_command_for_session(session_id)

Get the server command string that was used for a session.

Parameters:

Name Type Description Default
session_id str

Session identifier (format: "kind:workspace").

required

Returns:

Type Description
str | None

The command string that successfully started the server, or None.

Source code in src/code_context_agent/tools/lsp/session.py
def get_server_command_for_session(self, session_id: str) -> str | None:
    """Get the server command string that was used for a session.

    Args:
        session_id: Session identifier (format: "kind:workspace").

    Returns:
        The command string that successfully started the server, or None.
    """
    return self._server_commands.get(session_id)

get_or_create async

get_or_create(
    server_kind, workspace_path, startup_timeout=None
)

Get existing session or create new one with fallback chain.

Tries each configured LSP server command in order. If one fails, falls back to the next. Stores which command succeeded for later inspection.

Parameters:

Name Type Description Default
server_kind str

Server type ("ts", "py", etc.).

required
workspace_path str

Absolute path to workspace root.

required
startup_timeout float | None

Maximum seconds to wait for server initialization. If None, uses the value from settings.

None

Returns:

Type Description
LspClient

Connected LspClient instance.

Raises:

Type Description
RuntimeError

If all server commands fail to start.

ValueError

If server kind is not supported.

Source code in src/code_context_agent/tools/lsp/session.py
async def get_or_create(
    self,
    server_kind: str,
    workspace_path: str,
    startup_timeout: float | None = None,
) -> LspClient:
    """Get existing session or create new one with fallback chain.

    Tries each configured LSP server command in order. If one fails,
    falls back to the next. Stores which command succeeded for later
    inspection.

    Args:
        server_kind: Server type ("ts", "py", etc.).
        workspace_path: Absolute path to workspace root.
        startup_timeout: Maximum seconds to wait for server initialization.
            If None, uses the value from settings.

    Returns:
        Connected LspClient instance.

    Raises:
        RuntimeError: If all server commands fail to start.
        ValueError: If server kind is not supported.
    """
    if startup_timeout is None:
        startup_timeout = get_settings().lsp_startup_timeout

    key = self._make_session_key(server_kind, workspace_path)

    if key in self._sessions:
        client = self._sessions[key]
        if client.is_connected:
            logger.debug(f"Reusing LSP session: {key}")
            return client
        # Client disconnected, remove and recreate
        logger.debug(f"Removing disconnected LSP session: {key}")
        del self._sessions[key]
        self._server_commands.pop(key, None)

    logger.info(f"Creating new LSP session: {key}")
    settings = get_settings()

    # Normalize kind for workspace config lookup
    kind = server_kind.lower()
    aliases = {"typescript": "ts", "python": "py", "javascript": "ts"}
    kind = aliases.get(kind, kind)

    commands = self._get_server_commands(server_kind)
    last_error: OSError | TimeoutError | RuntimeError | None = None

    for i, cmd in enumerate(commands):
        try:
            ws_settings = self._get_workspace_settings(kind)
            client = LspClient(
                request_timeout=float(settings.lsp_timeout),
                workspace_settings=ws_settings,
            )
            init_options = self._get_workspace_config(kind)
            await client.start(
                cmd,
                workspace_path,
                startup_timeout=startup_timeout,
                initialization_options=init_options,
            )
            self._sessions[key] = client
            self._server_commands[key] = " ".join(cmd)
            if i > 0:
                logger.info(f"LSP fallback succeeded with: {' '.join(cmd)}")
            return client
        except (OSError, TimeoutError, RuntimeError) as e:
            last_error = e
            if i < len(commands) - 1:
                logger.warning(
                    f"LSP server '{' '.join(cmd)}' failed: {e}. Trying next fallback...",
                )
            continue

    raise RuntimeError(
        f"All LSP servers failed for {server_kind}. Last error: {last_error}",
    )

get_session

get_session(session_id)

Get an existing session by ID.

Parameters:

Name Type Description Default
session_id str

Session identifier (format: "kind:workspace").

required

Returns:

Type Description
LspClient | None

LspClient if session exists and is connected, None otherwise.

Source code in src/code_context_agent/tools/lsp/session.py
def get_session(self, session_id: str) -> LspClient | None:
    """Get an existing session by ID.

    Args:
        session_id: Session identifier (format: "kind:workspace").

    Returns:
        LspClient if session exists and is connected, None otherwise.
    """
    client = self._sessions.get(session_id)
    if client and client.is_connected:
        return client
    return None

list_sessions

list_sessions()

List all active session IDs.

Returns:

Type Description
list[str]

List of session key strings.

Source code in src/code_context_agent/tools/lsp/session.py
def list_sessions(self) -> list[str]:
    """List all active session IDs.

    Returns:
        List of session key strings.
    """
    return [key for key, client in self._sessions.items() if client.is_connected]

shutdown_session async

shutdown_session(session_id)

Shutdown a specific session.

Parameters:

Name Type Description Default
session_id str

Session identifier to shutdown.

required

Returns:

Type Description
bool

True if session was found and shutdown, False otherwise.

Source code in src/code_context_agent/tools/lsp/session.py
async def shutdown_session(self, session_id: str) -> bool:
    """Shutdown a specific session.

    Args:
        session_id: Session identifier to shutdown.

    Returns:
        True if session was found and shutdown, False otherwise.
    """
    client = self._sessions.pop(session_id, None)
    self._server_commands.pop(session_id, None)
    if client:
        await client.shutdown()
        logger.info(f"Shutdown LSP session: {session_id}")
        return True
    return False

shutdown_all async

shutdown_all()

Shutdown all active LSP sessions.

Source code in src/code_context_agent/tools/lsp/session.py
async def shutdown_all(self) -> None:
    """Shutdown all active LSP sessions."""
    logger.info(f"Shutting down {len(self._sessions)} LSP sessions")
    for key, client in list(self._sessions.items()):
        try:
            await client.shutdown()
            logger.debug(f"Shutdown LSP session: {key}")
        except (OSError, TimeoutError, RuntimeError) as e:
            logger.warning(f"Error shutting down LSP session {key}: {e}")
    self._sessions.clear()
    self._server_commands.clear()

get_session_manager

get_session_manager()

Get the LSP session manager singleton.

Returns:

Type Description
LspSessionManager

The global LspSessionManager instance.

Source code in src/code_context_agent/tools/lsp/session.py
def get_session_manager() -> LspSessionManager:
    """Get the LSP session manager singleton.

    Returns:
        The global LspSessionManager instance.
    """
    return LspSessionManager()