fix(sdk): align builtin routing and query error handling

This commit is contained in:
LogosVeritas committed 2026-10-28 15:54:34 +08:00
1 parent 2b9badbc37
commit ae334bd17e
44 files changed
+455 -178

No files matched your search

+4 -17
View File
@@ -218,13 +218,9 @@ class Session:
raise ValueError("run delivery must be queue or steer")
self.harness.start()
with self.harness.client.subscribe_session_notifications(self.id) as subscription:
response = self.harness.client._request_raw("session/query", {
"sessionId": self.id, "contentBlocks": normalize_input(input), "delivery": delivery,
})
if not isinstance(response, dict):
raise SdkProtocolError("session/query response must be a JSON object")
if response.get("kind") == "command":
execution = CommandExecution.model_validate(response.get("execution"))
response = self.harness.client._query(self.id, input, delivery)
if isinstance(response, CommandExecution):
execution = response
while True:
try:
notification = subscription._next_queued()
@@ -237,10 +233,7 @@ class Session:
if _is_command_done(notification, self.id, execution.command_id):
break
return execution
if response.get("kind") != "prompt" or not isinstance(response.get("messageId"), str) or not response["messageId"]:
raise SdkProtocolError("session/query requires a prompt messageId or command execution result")
message_id = response["messageId"]
activity = _PromptActivity(self.id, message_id)
activity = _PromptActivity(self.id, response)
while True:
notification = subscription.next()
if not activity.record(notification, on_notification):
@@ -278,12 +271,6 @@ def _is_command_done(notification: Notification, session_id: str, command_id: st
return isinstance(data, dict) and data.get("commandId") == command_id
def normalize_input(input: str | list[JsonObject]) -> list[JsonObject]:
if isinstance(input, str):
return [{"type": "text", "text": input}]
return input
def final_response(events: list[JsonObject]) -> str:
for event in reversed(events):
if event.get("type") != "assistant/message":
+27 -7
View File
@@ -10,9 +10,9 @@ import uuid
from collections import deque
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, TypeAlias, TypeVar
from typing import Callable, Literal, TypeAlias, TypeVar
from pydantic import BaseModel
from pydantic import BaseModel, ValidationError
from .errors import JsonRpcError, SdkProtocolError, TransportClosedError
from .models import CommandExecution, IncomingRequest, InitializeResponse, JsonObject, JsonValue, Notification, UserQuestionAnswer, UserQuestionRequest
@@ -227,11 +227,23 @@ class HarnessClient:
def session_command(self, session_id: str, line: str) -> CommandExecution:
"""Execute an explicit command and return its handler result without waiting for a turn."""
return self.request(
"session/command",
{"sessionId": session_id, "line": line},
response_model=CommandExecution,
)
result = self._request_raw("session/command", {"sessionId": session_id, "line": line})
return _command_execution(result, "session/command")
def _query(
self, session_id: str, input: str | list[JsonObject], delivery: Literal["queue", "steer"],
) -> str | CommandExecution:
"""Return a validated prompt receipt or command result for high-level run callers."""
blocks = [{"type": "text", "text": input}] if isinstance(input, str) else input
result = self._request_raw("session/query", {
"sessionId": session_id, "contentBlocks": blocks, "delivery": delivery,
})
if isinstance(result, dict):
if result.get("kind") == "prompt" and isinstance(result.get("messageId"), str) and result["messageId"]:
return result["messageId"]
if result.get("kind") == "command":
return _command_execution(result.get("execution"), "session/query")
raise SdkProtocolError("session/query requires a prompt messageId or command execution result")
def get_working_directory(self, session_id: str) -> str:
"""Read the effective directory, recovering a missing directory to the Session origin."""
@@ -727,3 +739,11 @@ class _EmptyResponse(BaseModel):
def _int_or_none(value: object) -> int | None:
return value if isinstance(value, int) else None
def _command_execution(value: JsonValue, method: str) -> CommandExecution:
"""Decode native command results and report malformed data as a protocol error."""
try:
return CommandExecution.model_validate(value)
except ValidationError as error:
raise SdkProtocolError(f"{method} returned an invalid command result") from error