diff --git a/coding-agent/src/coding_agent/ui/http_server.py b/coding-agent/src/coding_agent/ui/http_server.py index 7eaa8b67a..0edcbc85a 100644 --- a/coding-agent/src/coding_agent/ui/http_server.py +++ b/coding-agent/src/coding_agent/ui/http_server.py @@ -23,7 +23,8 @@ from agentkit.config.loader import load_config as load_agent_toml from agentkit.errors import ConfigError -from agentkit.tape.extract import ToolCallRecord, TurnTrace, extract_turns +from agentkit.result.reducers import result_from_turn_trace +from agentkit.tape.extract import TurnTrace, extract_turns from agentkit.tape.tape import Tape from coding_agent.approval import ApprovalPolicy from coding_agent.environment import ( @@ -116,15 +117,6 @@ # Constants APPROVAL_TIMEOUT_SECONDS = 120 - -_SESSION_RESULT_TOOL_DETAIL_KEYS = ( - "command", - "path", - "file_path", - "pattern", -) -_SESSION_RESULT_MAX_TOOL_ITEMS = 5 -_SESSION_RESULT_MAX_TOOL_DETAIL_CHARS = 160 SESSION_IDLE_TIMEOUT_MINUTES = 30 _SERVER_CONFIG_ENV = "CODING_AGENT_SERVER_CONFIG" _GITHUB_API_BASE_URL = "https://api.github.com" @@ -2153,44 +2145,6 @@ async def _get_visible_session( return session -def _compact_session_result_text(text: str, *, max_chars: int) -> str: - compact = " ".join(text.split()) - if len(compact) <= max_chars: - return compact - return compact[: max_chars - 3].rstrip() + "..." - - -def _session_result_tool_detail(call: ToolCallRecord) -> str | None: - for key in _SESSION_RESULT_TOOL_DETAIL_KEYS: - value = call.arguments.get(key) - if not isinstance(value, str) or value.strip() == "": - continue - return _compact_session_result_text( - value, - max_chars=_SESSION_RESULT_MAX_TOOL_DETAIL_CHARS, - ) - return None - - -def _session_result_verification_summary(turn: TurnTrace | None) -> str | None: - if turn is None or not turn.tool_calls: - return None - - items: list[str] = [] - for call in turn.tool_calls[:_SESSION_RESULT_MAX_TOOL_ITEMS]: - name = call.name.strip() or "unnamed_tool" - detail = _session_result_tool_detail(call) - if detail is None: - items.append(name) - else: - items.append(f"{name}: {detail}") - - remaining = len(turn.tool_calls) - len(items) - if remaining > 0: - items.append(f"+{remaining} more") - return "Tool activity: " + "; ".join(items) - - def _session_runtime_tape(session: Session) -> Tape | None: runtime_ctx = session.runtime_ctx if runtime_ctx is None: @@ -2240,6 +2194,12 @@ async def get_session_result( session = await _get_visible_session(session_id, auth_context) summary = session.as_dict() latest_turn = await _session_result_latest_turn(session) + turn_result = None if latest_turn is None else result_from_turn_trace(latest_turn) + verification_summary = ( + None + if turn_result is None or turn_result.verification_summary is None + else turn_result.verification_summary.summary + ) return SessionResultResponse( session_id=session.id, status=summary["status"], @@ -2249,8 +2209,8 @@ async def get_session_result( origin=session.origin, provider_name=session.provider_name, model_name=session.model_name, - final_answer=None if latest_turn is None else latest_turn.final_output, - verification_summary=_session_result_verification_summary(latest_turn), + final_answer=None if turn_result is None else turn_result.final_output, + verification_summary=verification_summary, failure_details=_session_result_failure_details(session), ) diff --git a/coding-agent/tests/ui/test_http_server.py b/coding-agent/tests/ui/test_http_server.py index 08c939fae..a27259702 100644 --- a/coding-agent/tests/ui/test_http_server.py +++ b/coding-agent/tests/ui/test_http_server.py @@ -25,6 +25,8 @@ from agentkit.errors import ConfigError from agentkit.checkpoint.models import CheckpointMeta from agentkit.providers.models import DoneEvent, TextEvent, ToolCallEvent +from agentkit.result.models import TurnResult, VerificationSummary +from agentkit.tape.extract import TurnTrace from agentkit.tape.models import Entry from agentkit.tape.tape import Tape from agentkit.tools import FatalToolExecutionError @@ -3769,6 +3771,51 @@ async def test_get_session_success(self, client): class TestRemoteResultPublicationContract: + async def test_session_result_endpoint_uses_agentkit_reducer_without_response_change( + self, client: AsyncClient, monkeypatch: pytest.MonkeyPatch + ) -> None: + session = register_session( + "result-agentkit-reducer", + execution_binding=CloudWorkspaceBinding( + workspace_url="docker://agent-ws-result/workspace", + workspace_id="ws-agentkit-result", + ), + provider_name="openai", + model_name="result-model", + ) + tape = Tape(tape_id="runtime-tape") + tape.append( + Entry(kind="message", payload={"role": "user", "content": "fix it"}) + ) + tape.append( + Entry(kind="message", payload={"role": "assistant", "content": "ignored"}) + ) + session.runtime_ctx = SimpleNamespace(tape=tape) + session.tape_id = tape.tape_id + + def fake_result_from_turn_trace(turn: TurnTrace) -> TurnResult: + assert turn.user_input == "fix it" + return TurnResult( + final_output="Reducer final answer.", + verification_summary=VerificationSummary( + summary="Reducer verification summary." + ), + ) + + monkeypatch.setattr( + http_server, + "result_from_turn_trace", + fake_result_from_turn_trace, + ) + + response = await client.get(f"/sessions/{session.id}/result") + + assert response.status_code == 200 + data = response.json() + assert data["final_answer"] == "Reducer final answer." + assert data["verification_summary"] == "Reducer verification summary." + assert data["failure_details"] is None + async def test_session_result_includes_final_answer_and_tool_activity_from_runtime_tape( self, client: AsyncClient ) -> None: