fix: align Lingma tool event lifecycle handling

Handle tool/invokeResult and richer tool/call/sync payloads in the client,
and document/retest the verified VSCode monitoring workflow for tool events.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
GitHub Actions
2026-04-19 09:49:01 +08:00
parent 1c7b86e2c0
commit 5aa7fbfae5
3 changed files with 147 additions and 25 deletions

View File

@@ -106,23 +106,36 @@ class LspWsRpcClient:
@staticmethod
def _extract_tool_event(params: dict[str, Any]) -> dict[str, Any] | None:
candidates: list[dict[str, Any]] = []
if isinstance(params.get("toolCall"), dict):
candidates.append(params["toolCall"])
if isinstance(params.get("tool_call"), dict):
candidates.append(params["tool_call"])
if isinstance(params.get("tool"), dict):
candidates.append(params["tool"])
def add_candidate(obj: Any) -> None:
if isinstance(obj, dict):
candidates.append(obj)
add_candidate(params.get("toolCall"))
add_candidate(params.get("tool_call"))
add_candidate(params.get("tool"))
data = params.get("data")
if isinstance(data, dict):
if isinstance(data.get("toolCall"), dict):
candidates.append(data["toolCall"])
if isinstance(data.get("tool_call"), dict):
candidates.append(data["tool_call"])
if isinstance(data.get("tool"), dict):
candidates.append(data["tool"])
add_candidate(data.get("toolCall"))
add_candidate(data.get("tool_call"))
add_candidate(data.get("tool"))
results = params.get("results")
if isinstance(results, list):
for item in results:
add_candidate(item)
if not candidates:
return None
fallback_id = params.get("toolCallId") or params.get("tool_call_id")
if not fallback_id:
return None
return {
"id": str(fallback_id),
"name": str(params.get("name") or "tool"),
"input": params.get("parameters") or {},
"result": params.get("result"),
}
raw = candidates[0]
tool_id = (
@@ -132,28 +145,42 @@ class LspWsRpcClient:
or params.get("toolCallId")
or params.get("tool_call_id")
)
name = raw.get("name") or raw.get("toolName") or raw.get("tool_name")
name = (
raw.get("name")
or raw.get("toolName")
or raw.get("tool_name")
or params.get("name")
)
call_input = raw.get("input")
if call_input is None:
call_input = raw.get("arguments")
if call_input is None:
call_input = raw.get("args")
if call_input is None:
call_input = raw.get("parameters")
if call_input is None:
call_input = params.get("parameters")
result_payload = raw.get("result")
if result_payload is None:
result_payload = params.get("result")
if result_payload is None and isinstance(data, dict):
result_payload = data.get("result")
if result_payload is None and isinstance(raw.get("results"), list):
result_payload = raw.get("results")
if not tool_id:
return None
return {
event: dict[str, Any] = {
"id": str(tool_id),
"name": str(name or "tool"),
"input": call_input if call_input is not None else {},
"result": result_payload,
}
if result_payload is not None:
event["result"] = result_payload
return event
async def start(self):
self._reader_task = asyncio.create_task(self._reader_loop())
@@ -239,14 +266,23 @@ class LspWsRpcClient:
stream["first_chunk_at"] = time.monotonic()
stream["chunks"].put_nowait({"type": "text", "text": text})
if method in {"tool/call/sync", "tool/invoke", "tool/call/approve"}:
if method in {"tool/call/sync", "tool/invoke", "tool/call/approve", "tool/invokeResult"}:
tool_event = self._extract_tool_event(params)
req_id = params.get("requestId")
stream = self._chat_streams.get(req_id)
if stream is not None:
tool_event = self._extract_tool_event(params)
if tool_event is not None:
stream["tool_events"].append(tool_event)
stream["chunks"].put_nowait({"type": "tool", "tool": tool_event})
stream = self._chat_streams.get(req_id) if req_id else None
if stream is None and tool_event is not None:
for item in self._chat_streams.values():
if any(evt.get("id") == tool_event["id"] for evt in item["tool_events"]):
stream = item
break
if stream is None and len(self._chat_streams) == 1:
stream = next(iter(self._chat_streams.values()))
if stream is not None and tool_event is not None:
stream["tool_events"].append(tool_event)
stream["chunks"].put_nowait({"type": "tool", "tool": tool_event})
if method == "chat/finish":
req_id = params.get("requestId")