Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
# Changelog

## [1.1.6](https://github.com/trpc-group/trpc-agent-python/releases/tag/v1.1.6) (2026-06-03)

### Features

* Skill: Added a recoverable Cube sandbox runtime for skills, including `CubeClientConfig`, a unified `create_cube_sandbox_client` entry point, optional `auto_recover` support in `CubeSandboxClient`, sandbox lifecycle helpers, and direct `CubeWorkspaceRuntime` creation from the client.
* Skill: Unified skill load/run/exec/stager paths around repository-level workspace runtime resolution via `repository.get_workspace_runtime(ctx)`, so tools under the same skill repository share one workspace runtime context.
* MCP: Added MCP tool caching to avoid repeated network access.
* Tools: Added `GraphAgent` support in `AgentTool`, allowing wrapped graph agents to return results from tool context state.
* Examples/Eval: Restored evaluation examples that were previously removed during open-source cleanup.
* Optimizer: Added support for the prompt self-optimization `AgentOptimizer`.

### Bug Fixes

* Storage: Fixed frequent sqlite warnings in `SqlSessionService` by consistently using database-side `func.now()` for update timestamps.

## [1.1.5](https://github.com/trpc-group/trpc-agent-python/releases/tag/v1.1.5) (2026-05-19)

### Features

* Tools: Added `StreamingProgressTool` with matching `ToolsProcessor` plumbing so tools can surface intermediate progress as `partial=True` events while still emitting a single final `function_response`; included `BaseTool` streaming hooks, the `llmagent_with_streaming_progress_tool` example and verification script.
* Eval: Added `RemoteEvalService` to drive evaluations against agents exposed over remote interfaces, refactored `AgentEvaluator` to support remote agent calls, and expanded English/Chinese evaluation docs.
* Model: Landed the OpenAI-compatible adapter layer (`models/openai_adapter/{_base,_deepseek,_hunyuan}.py`) that isolates provider-specific behavior from `OpenAIModel`, including DeepSeek v4 thinking / `response_format` / `reasoning_content` / token usage handling and hy3-preview ToolPrompt text parsing with streaming filter.
* Examples: Added `examples/mempalace_mcp` (MemPalace via MCP) and updated `examples/llmagent_with_thinking` to enable `add_tools_to_prompt` only for hy3-preview and display thinking / tool calls / final answer separately.

* Utils: Added `json_loads_repair` and `json_repair_string` helpers (backed by `json_repair`) under `trpc_agent_sdk.utils`, with full unit test coverage.
* Model/Tools: Adopted `json_repair` only on JSON-tolerant paths — `JsonToolPrompt` / `XmlToolPrompt` parse_function, non-streaming OpenAI tool-call args, `AgentTool` structured-output validation, skills tool result parsing — while keeping strict `json.loads` for the streaming tool-call accumulator (to preserve "wait for next chunk" semantics) and Hunyuan plain-text `<arg_value>` parsing (to avoid silently coercing plain text into empty strings).
* Model: Fixed ToolPrompt streaming parsing so multiple tool calls in a single response are all preserved instead of only the last one being kept.

### Bug Fixes

* Teams: TeamAgent now honors `actions.skip_summarization` from custom tool events, so tools like `AgentTool(skip_summarization=True)` and `StreamingProgressTool(skip_summarization=True)` end the leader loop without an extra summarization turn (previously masked by leader's `disable_react_tool=True`).

## [1.1.4](https://github.com/trpc-group/trpc-agent-python/releases/tag/v1.1.4) (2026-05-13)

### Bug Fixes

* Tools: Removed default `mempalace_tool` exports from `trpc_agent_sdk.tools` to avoid forcing MemPalace optional dependencies during base package import.

## [1.1.3](https://github.com/trpc-group/trpc-agent-python/releases/tag/v1.1.3) (2026-05-12)

### Features
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ classifiers = [
dependencies = [
"pydantic>=2.11.3",
"openai>=1.3.0",
"mcp<1.23.4,>=1.10.1",
"mcp>=1.10.1",
"aiohttp",
"httpx>=0.27.0",
"httpx-sse>=0.4.0",
Expand Down
25 changes: 25 additions & 0 deletions tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ def test_init_with_required_params(self, mock_agent, mock_session_service):
assert runner.session_service == mock_session_service
assert runner.artifact_service is None
assert runner.memory_service is None
assert runner._close_session_service_on_close is True
assert runner._close_memory_service_on_close is True

def test_init_with_all_params(self, mock_agent, mock_session_service):
"""Test Runner initialization with all parameters."""
Expand All @@ -119,10 +121,14 @@ def test_init_with_all_params(self, mock_agent, mock_session_service):
session_service=mock_session_service,
artifact_service=artifact_service,
memory_service=memory_service,
close_session_service_on_close=False,
close_memory_service_on_close=False,
)

assert runner.artifact_service == artifact_service
assert runner.memory_service == memory_service
assert runner._close_session_service_on_close is False
assert runner._close_memory_service_on_close is False


# Tests for run_async method
Expand Down Expand Up @@ -618,6 +624,25 @@ async def test_close_calls_all_services(self, runner, mock_session_service):
mock_session_service.close.assert_called_once()
memory_service.close.assert_called_once()

@pytest.mark.asyncio
async def test_close_can_keep_external_services_open(self, mock_agent, mock_session_service):
"""Test close skips externally managed session and memory services."""
memory_service = AsyncMock(spec=BaseMemoryService)
runner = Runner(
app_name="test_app",
agent=mock_agent,
session_service=mock_session_service,
memory_service=memory_service,
close_session_service_on_close=False,
close_memory_service_on_close=False,
)

with patch.object(runner, '_collect_toolset', return_value=set()):
await runner.close()

mock_session_service.close.assert_not_called()
memory_service.close.assert_not_called()


# Tests for session history handling
class TestSessionHistory:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@

def test_version():
"""Test the version module."""
assert __version__ == '1.1.3'
assert __version__ == '1.1.6'
4 changes: 2 additions & 2 deletions tests/tools/mcp_tool/test_mcp_session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,11 @@ def test_sse_client(self, mock_sse):
call_kwargs = mock_sse.call_args
assert call_kwargs.kwargs["headers"] == {"Auth": "token"}

@patch("trpc_agent_sdk.tools.mcp_tool._mcp_session_manager.streamablehttp_client")
@patch.object(MCPSessionManager, "_create_streamable_http_client")
def test_streamable_client(self, mock_streamable):
mgr = MCPSessionManager(connection_params=_streamable_conn())
mgr._create_client({"Auth": "token"})
mock_streamable.assert_called_once()
mock_streamable.assert_called_once_with({"Auth": "token"})

def test_unsupported_params_raises(self):
mgr = MCPSessionManager(connection_params=_stdio_conn())
Expand Down
15 changes: 13 additions & 2 deletions trpc_agent_sdk/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ def __init__(
enable_post_turn_processing: bool = True,
defer_post_turn_processing: bool = False,
post_turn_queue_maxsize: int = 256,
close_session_service_on_close: bool = True,
close_memory_service_on_close: bool = True,
):
"""Initializes the Runner.

Expand All @@ -209,6 +211,13 @@ def __init__(
persistence run in a dedicated thread/event loop so request
completion is not blocked by post-turn I/O/LLM latency.
post_turn_queue_maxsize: Max buffered post-turn jobs per runner.
close_session_service_on_close: Whether :meth:`close` should close
the session service. Set to False when the service is managed
outside the runner, e.g. an application-level Redis connection
pool shared by many short-lived runners.
close_memory_service_on_close: Whether :meth:`close` should close
the memory service. Set to False when the service is managed
outside the runner.
"""
self.app_name = app_name
self.agent = agent
Expand All @@ -219,6 +228,8 @@ def __init__(
self._defer_post_turn_processing = defer_post_turn_processing
self._post_turn_thread: _PostTurnWorkerThread | None = None
self._post_turn_queue_maxsize = max(1, int(post_turn_queue_maxsize))
self._close_session_service_on_close = close_session_service_on_close
self._close_memory_service_on_close = close_memory_service_on_close

async def _run_post_turn_processing(
self,
Expand Down Expand Up @@ -823,7 +834,7 @@ async def close(self):
"""
await self._shutdown_post_turn_worker()
await self._cleanup_toolsets(self._collect_toolset(self.agent))
if self.session_service:
if self.session_service and self._close_session_service_on_close:
await self.session_service.close()
if self.memory_service:
if self.memory_service and self._close_memory_service_on_close:
await self.memory_service.close()
54 changes: 46 additions & 8 deletions trpc_agent_sdk/tools/mcp_tool/_mcp_session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,28 @@

import asyncio
import hashlib
import inspect
import json
import sys
from contextlib import AsyncExitStack
from contextlib import asynccontextmanager
from datetime import timedelta
from typing import Dict
from typing import Optional
from typing import Union

# cSpell:ignore streamable

import httpx
from mcp import ClientSession
from mcp.client.sse import sse_client
from mcp.client.stdio import stdio_client
from mcp.client.streamable_http import streamablehttp_client
try:
from mcp.client.streamable_http import streamable_http_client
except ImportError: # pragma: no cover - compatibility with older mcp versions
from mcp.client.streamable_http import streamablehttp_client as streamable_http_client

_STREAMABLE_HTTP_CLIENT_SUPPORTS_HTTP_CLIENT = "http_client" in inspect.signature(streamable_http_client).parameters

from trpc_agent_sdk.log import logger

Expand Down Expand Up @@ -161,7 +171,7 @@ def _create_client(self, merged_headers: Optional[Dict[str, str]] = None):
if isinstance(self._connection_params, StdioConnectionParams):
client = stdio_client(
server=self._connection_params.server_params,
errlog=sys.stderr,
errlog=sys.stderr, # cSpell:ignore errlog
)
elif isinstance(self._connection_params, SseConnectionParams):
client = sse_client(
Expand All @@ -171,18 +181,46 @@ def _create_client(self, merged_headers: Optional[Dict[str, str]] = None):
sse_read_timeout=self._connection_params.sse_read_timeout,
)
elif isinstance(self._connection_params, StreamableHTTPConnectionParams):
client = streamablehttp_client(
client = self._create_streamable_http_client(merged_headers)
else:
raise ValueError('Unable to initialize connection. Connection should be'
' StdioConnectionParams or SseConnectionParams or StreamableHTTPConnectionParams, but got'
f' {type(self._connection_params)}')
return client

def _create_streamable_http_client(self, merged_headers: Optional[Dict[str, str]] = None):
"""Creates a Streamable HTTP client with compatibility for MCP SDK versions."""
if not _STREAMABLE_HTTP_CLIENT_SUPPORTS_HTTP_CLIENT:
return streamable_http_client(
url=self._connection_params.url,
headers=merged_headers,
timeout=self._connection_params.timeout,
sse_read_timeout=self._connection_params.sse_read_timeout,
terminate_on_close=self._connection_params.terminate_on_close,
)
else:
raise ValueError('Unable to initialize connection. Connection should be'
' StdioConnectionParams or SseConnectionParams or StreamableHTTPConnectionParams, but got'
f' {type(self._connection_params)}')
return client

return self._create_streamable_http_client_with_httpx(merged_headers)

@asynccontextmanager
async def _create_streamable_http_client_with_httpx(self, merged_headers: Optional[Dict[str, str]] = None):
"""Creates a new-style Streamable HTTP client and owns its httpx client."""
timeout_seconds = self._connection_params.timeout
if isinstance(timeout_seconds, timedelta):
timeout_seconds = timeout_seconds.total_seconds()
sse_read_timeout_seconds = self._connection_params.sse_read_timeout
if isinstance(sse_read_timeout_seconds, timedelta):
sse_read_timeout_seconds = sse_read_timeout_seconds.total_seconds()

async with httpx.AsyncClient(
headers=merged_headers,
timeout=httpx.Timeout(timeout_seconds, read=sse_read_timeout_seconds),
) as http_client:
async with streamable_http_client(
url=self._connection_params.url,
http_client=http_client,
terminate_on_close=self._connection_params.terminate_on_close,
) as transports:
yield transports

async def create_session(self, headers: Optional[Dict[str, str]] = None) -> ClientSession | None:
"""Creates and initializes an MCP client session.
Expand Down
2 changes: 1 addition & 1 deletion trpc_agent_sdk/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
This module defines the version information for TRPC Agent
"""

__version__ = '1.1.3'
__version__ = '1.1.6'
Loading