Skip to content
Closed
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
80 changes: 79 additions & 1 deletion sdks/python/agenta/sdk/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,47 @@
TraceContext,
to_messages,
)
from .errors import UnsupportedHarnessError
from .errors import ToolResolutionError, UnsupportedHarnessError
from .interfaces import Backend, Environment, Harness, Sandbox, Session
from .mcp import (
MCPConfigurationError,
MCPError,
MCPResolver,
MCPServerConfig,
MissingMCPSecretError,
ResolvedMCPServer,
)
from .streaming import AgentRun
from .tools import (
BuiltinToolConfig,
CallbackToolSpec,
ClientToolConfig,
ClientToolSpec,
CodeToolConfig,
CodeToolSpec,
DuplicateToolNameError,
EnvironmentToolSecretProvider,
GatewayToolResolver,
GatewayToolConfig,
GatewayToolResolution,
GatewayToolResolutionError,
MissingSecretPolicy,
MissingToolSecretError,
ResolvedToolSet,
ToolConfig,
ToolConfigError,
ToolConfigurationError,
ToolError,
ToolResolver,
ToolSecretProvider,
ToolSpec,
UnsupportedToolProviderError,
coerce_tool_config,
coerce_tool_configs,
parse_tool_config,
parse_tool_configs,
)
from .ui_messages import from_ui_messages, to_ui_message, ui_message_stream

__all__ = [
# DTOs
Expand All @@ -69,9 +107,48 @@
"AgentEvent",
"AgentResult",
"AgentRun",
# UI message codec (the /messages egress adapter)
"from_ui_messages",
"to_ui_message",
"ui_message_stream",
"TraceContext",
"ToolCallback",
"PermissionPolicy",
# Canonical tools API
"ToolConfig",
"BuiltinToolConfig",
"GatewayToolConfig",
"CodeToolConfig",
"ClientToolConfig",
"ToolSpec",
"CallbackToolSpec",
"CodeToolSpec",
"ClientToolSpec",
"ResolvedToolSet",
"GatewayToolResolution",
"ToolResolver",
"ToolSecretProvider",
"GatewayToolResolver",
"EnvironmentToolSecretProvider",
"MissingSecretPolicy",
"parse_tool_config",
"parse_tool_configs",
"coerce_tool_config",
"coerce_tool_configs",
"ToolError",
"ToolConfigError",
"ToolConfigurationError",
"GatewayToolResolutionError",
"UnsupportedToolProviderError",
"MissingToolSecretError",
"DuplicateToolNameError",
# MCP is a sibling subsystem
"MCPServerConfig",
"ResolvedMCPServer",
"MCPResolver",
"MCPError",
"MCPConfigurationError",
"MissingMCPSecretError",
# Interfaces (ports)
"Backend",
"Sandbox",
Expand All @@ -80,6 +157,7 @@
"Harness",
# Errors
"UnsupportedHarnessError",
"ToolResolutionError",
# Adapters
"RivetBackend",
"InProcessPiBackend",
Expand Down
46 changes: 14 additions & 32 deletions sdks/python/agenta/sdk/agents/adapters/harnesses.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
SessionConfig,
)
from ..interfaces import Environment, Harness
from ..tools.models import ToolSpec, coerce_tool_spec
from .agenta_builtins import (
AGENTA_FORCED_SKILLS,
compose_append_system,
Expand All @@ -37,8 +38,6 @@

log = get_module_logger(__name__)

_EMPTY_OBJECT_SCHEMA: Dict[str, Any] = {"type": "object", "properties": {}}


def _opt_str(value: Any) -> Any:
"""Keep a harness option only if it is a non-empty string; otherwise drop it to ``None``
Expand All @@ -48,29 +47,9 @@ def _opt_str(value: Any) -> Any:
return None


def _normalize_tool_specs(specs: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Coerce resolved tool specs into the shape every harness expects.

Drops malformed entries (no name) and fills the defaults the harness runtimes need: a
description (falls back to the name) and a JSON-Schema ``inputSchema`` (an empty object
when none was resolved). ``callRef`` is preserved so the call routes back to Agenta.
"""
normalized: List[Dict[str, Any]] = []
for spec in specs or []:
if not isinstance(spec, dict):
continue
name = spec.get("name")
if not name:
continue
normalized.append(
{
"name": name,
"description": spec.get("description") or name,
"inputSchema": spec.get("inputSchema") or dict(_EMPTY_OBJECT_SCHEMA),
"callRef": spec.get("callRef"),
}
)
return normalized
def _normalize_tool_specs(specs: List[Dict[str, Any]]) -> List[ToolSpec]:
"""Compatibility helper for callers still supplying runner dictionaries."""
return [coerce_tool_spec(spec) for spec in specs or []]


class PiHarness(Harness):
Expand All @@ -85,9 +64,10 @@ def _to_harness_config(self, config: SessionConfig) -> PiAgentConfig:
return PiAgentConfig(
agents_md=config.agent.instructions,
model=config.agent.model,
builtin_tools=list(config.builtin_tools),
custom_tools=_normalize_tool_specs(config.custom_tools),
builtin_names=list(config.builtin_names),
tool_specs=list(config.tool_specs),
tool_callback=config.tool_callback,
mcp_servers=list(config.mcp_servers),
system=_opt_str(pi_options.get("system")),
append_system=_opt_str(pi_options.get("append_system")),
)
Expand All @@ -100,16 +80,17 @@ def _to_harness_config(self, config: SessionConfig) -> ClaudeAgentConfig:
# Claude has no Pi built-in tools; drop them rather than ship a name Claude cannot
# honor. Tools go over MCP, and Claude gates tool use, so the permission policy is
# carried through.
if config.builtin_tools:
if config.builtin_names:
log.warning(
"ClaudeHarness ignores %d built-in tool(s); built-ins are a Pi concept",
len(config.builtin_tools),
len(config.builtin_names),
)
return ClaudeAgentConfig(
agents_md=config.agent.instructions,
model=config.agent.model,
custom_tools=_normalize_tool_specs(config.custom_tools),
tool_specs=list(config.tool_specs),
tool_callback=config.tool_callback,
mcp_servers=list(config.mcp_servers),
permission_policy=config.permission_policy,
)

Expand All @@ -130,9 +111,10 @@ def _to_harness_config(self, config: SessionConfig) -> AgentaAgentConfig:
return AgentaAgentConfig(
agents_md=compose_instructions(config.agent.instructions),
model=config.agent.model,
builtin_tools=force_tools(list(config.builtin_tools)),
custom_tools=_normalize_tool_specs(config.custom_tools),
builtin_names=force_tools(list(config.builtin_names)),
tool_specs=list(config.tool_specs),
tool_callback=config.tool_callback,
mcp_servers=list(config.mcp_servers),
system=_opt_str(pi_options.get("system")),
append_system=compose_append_system(
_opt_str(pi_options.get("append_system"))
Expand Down
Loading
Loading