Files
wyndham-ARR/arr_processing/open_agent.py
2026-07-29 16:38:05 +08:00

118 lines
4.8 KiB
Python

"""Adapter from the verified text Open Agent API to ProcessingTransport."""
from __future__ import annotations
import hashlib
from typing import Any, Callable, Mapping, Optional
from agent_integration.client import (
OpenAgentAPIError,
OpenAgentProtocolError,
OpenAgentTransportError,
)
from agent_integration.service import AgentResponseError, OpenAgentService
from arr_processing.contracts import PROCESSING_REQUEST_VERSION, ProcessingRequest
from arr_processing.errors import ProcessingTransportError
from arr_processing.runner import RemoteRunSnapshot
class OpenAgentProcessingTransport:
"""Uses only text Session/Run APIs; file resolution remains a runtime provider seam."""
def __init__(
self,
service: OpenAgentService,
*,
message_builder: Optional[Callable[[ProcessingRequest], str]] = None,
) -> None:
self._service = service
self._message_builder = message_builder or (lambda request: request.message())
def submit(self, request: ProcessingRequest, idempotency_key: str) -> RemoteRunSnapshot:
conversation_id = self.conversation_id(request.job_id)
try:
response = self._service.send_message(
conversation_id,
self._message_builder(request),
message_id=idempotency_key,
external_subject_id=conversation_id,
metadata={
"contract_version": PROCESSING_REQUEST_VERSION,
"processing_kind": "opera_daily",
},
)
except (OpenAgentAPIError, OpenAgentTransportError, OpenAgentProtocolError, AgentResponseError) as error:
self._raise_mapped(error)
return self._snapshot(response)
def get(self, request: ProcessingRequest, remote_run_id: str) -> RemoteRunSnapshot:
try:
response = self._service.get_run(
self.conversation_id(request.job_id),
remote_run_id,
)
except (OpenAgentAPIError, OpenAgentTransportError, OpenAgentProtocolError, AgentResponseError) as error:
self._raise_mapped(error)
return self._snapshot(response, expected_run_id=remote_run_id)
def cancel(self, request: ProcessingRequest, remote_run_id: str) -> RemoteRunSnapshot:
try:
response = self._service.cancel_run(
self.conversation_id(request.job_id),
remote_run_id,
)
except (OpenAgentAPIError, OpenAgentTransportError, OpenAgentProtocolError, AgentResponseError) as error:
self._raise_mapped(error)
return self._snapshot(response, expected_run_id=remote_run_id)
@staticmethod
def conversation_id(job_id: str) -> str:
digest = hashlib.sha256(job_id.encode("utf-8")).hexdigest()
return "arrproc_" + digest[:48]
@staticmethod
def _snapshot(
response: Mapping[str, Any],
expected_run_id: Optional[str] = None,
) -> RemoteRunSnapshot:
if not isinstance(response, Mapping):
raise ProcessingTransportError("PROCESSING_REMOTE_PROTOCOL_INVALID", retryable=False)
run_id = response.get("run_id")
status = response.get("status")
final_content = response.get("final_content")
if (
not isinstance(run_id, str)
or not run_id
or not isinstance(status, str)
or not status
or (expected_run_id is not None and run_id != expected_run_id)
or (final_content is not None and not isinstance(final_content, str))
):
raise ProcessingTransportError("PROCESSING_REMOTE_PROTOCOL_INVALID", retryable=False)
return RemoteRunSnapshot(run_id, status.lower(), final_content)
@staticmethod
def _raise_mapped(error: BaseException) -> None:
if isinstance(error, OpenAgentAPIError):
if error.active_run_conflict:
raise ProcessingTransportError(
"PROCESSING_ACTIVE_RUN_CONFLICT",
retryable=True,
active_run_conflict=True,
) from None
if error.retryable:
code = (
"PROCESSING_REMOTE_RATE_LIMITED"
if error.status_code == 429
else "PROCESSING_REMOTE_UNAVAILABLE"
)
raise ProcessingTransportError(code, retryable=True) from None
raise ProcessingTransportError("PROCESSING_REMOTE_REJECTED", retryable=False) from None
if isinstance(error, OpenAgentTransportError):
raise ProcessingTransportError(
"PROCESSING_REMOTE_UNAVAILABLE", retryable=True
) from None
raise ProcessingTransportError(
"PROCESSING_REMOTE_PROTOCOL_INVALID", retryable=False
) from None