fix: emit Lingma tool approve/invoke roundtrip
Forward tool/call/sync and tool/invoke events to Lingma with auto-approve and invokeResult so tool calls can complete end-to-end. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -101,6 +101,7 @@ class LspWsRpcClient:
|
||||
self._rx_buffer = b""
|
||||
self._chat_streams: dict[str, dict] = {}
|
||||
self._tool_stream_map: dict[str, str] = {}
|
||||
self._tool_roundtrip_done: set[str] = set()
|
||||
self._on_disconnect = on_disconnect
|
||||
self._closed = False
|
||||
|
||||
@@ -204,6 +205,7 @@ class LspWsRpcClient:
|
||||
stream["chunks"].put_nowait(None)
|
||||
self._chat_streams.clear()
|
||||
self._tool_stream_map.clear()
|
||||
self._tool_roundtrip_done.clear()
|
||||
|
||||
async def _send(self, payload: dict):
|
||||
async with self._send_lock:
|
||||
@@ -320,6 +322,55 @@ class LspWsRpcClient:
|
||||
return merged, changed
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _is_tool_roundtrip_method(method: str | None) -> bool:
|
||||
return method in {"tool/call/sync", "tool/invoke"}
|
||||
|
||||
@staticmethod
|
||||
def _build_tool_approve_params(params: dict[str, Any], tool_id: str) -> dict[str, Any] | None:
|
||||
req_id = params.get("requestId")
|
||||
session_id = params.get("sessionId")
|
||||
if not isinstance(req_id, str) or not req_id.strip():
|
||||
return None
|
||||
if not isinstance(session_id, str) or not session_id.strip():
|
||||
return None
|
||||
return {
|
||||
"type": "tool_call",
|
||||
"sessionId": session_id,
|
||||
"requestId": req_id,
|
||||
"toolCallId": tool_id,
|
||||
"approval": True,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _build_tool_invoke_result_params(params: dict[str, Any], tool_event: dict[str, Any], tool_id: str) -> dict[str, Any]:
|
||||
return {
|
||||
"toolCallId": tool_id,
|
||||
"name": str(tool_event.get("name") or params.get("name") or "tool"),
|
||||
"success": True,
|
||||
"errorMessage": "",
|
||||
"result": tool_event.get("result") if "result" in tool_event else {},
|
||||
}
|
||||
|
||||
async def _maybe_emit_tool_roundtrip(self, method: str, params: dict[str, Any], tool_event: dict[str, Any]) -> None:
|
||||
if not self._is_tool_roundtrip_method(method):
|
||||
return
|
||||
tool_id = self._normalize_tool_id(method, params, tool_event)
|
||||
if not tool_id:
|
||||
return
|
||||
if tool_id in self._tool_roundtrip_done:
|
||||
return
|
||||
|
||||
approve_params = self._build_tool_approve_params(params, tool_id)
|
||||
if approve_params is None:
|
||||
return
|
||||
|
||||
self._tool_roundtrip_done.add(tool_id)
|
||||
await self.notify("tool/call/approve", approve_params)
|
||||
invoke_result_params = self._build_tool_invoke_result_params(params, tool_event, tool_id)
|
||||
await self.notify("tool/invokeResult", invoke_result_params)
|
||||
|
||||
|
||||
def _resolve_tool_stream(self, method: str, params: dict[str, Any], tool_event: dict[str, Any] | None) -> dict | None:
|
||||
req_id = params.get("requestId")
|
||||
if isinstance(req_id, str) and req_id.strip():
|
||||
@@ -363,6 +414,7 @@ class LspWsRpcClient:
|
||||
if not tool_id:
|
||||
logger.warning("drop unroutable tool event: method=%s missing tool id", method)
|
||||
else:
|
||||
await self._maybe_emit_tool_roundtrip(method, params, tool_event)
|
||||
tool_states = stream["tool_states"]
|
||||
order = stream["tool_order"]
|
||||
existing = tool_states.get(tool_id)
|
||||
@@ -431,6 +483,7 @@ class LspWsRpcClient:
|
||||
for tool_id, mapped_req in list(self._tool_stream_map.items()):
|
||||
if mapped_req == request_id:
|
||||
self._tool_stream_map.pop(tool_id, None)
|
||||
self._tool_roundtrip_done.discard(tool_id)
|
||||
# Drain queue so no stray future gets stuck if the consumer bailed early.
|
||||
if not stream["done"].is_set():
|
||||
stream["done"].set()
|
||||
|
||||
Reference in New Issue
Block a user