fix(sdk): align builtin routing and query error handling
This commit is contained in:
1 parent
2b9badbc37
commit
ae334bd17e
44 files changed
+455
-178
No files matched your search
@@ -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":
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user