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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user