Skip to content
29 changes: 29 additions & 0 deletions agentrun/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@
SandboxClient,
Template,
)
# Super Agent
from agentrun.super_agent import (
ConversationInfo,
InvokeResponseData,
InvokeStream,
)
from agentrun.super_agent import Message as SuperAgentMessage
from agentrun.super_agent import (
SSEEvent,
SuperAgent,
SuperAgentClient,
SuperAgentCreateInput,
SuperAgentListInput,
SuperAgentUpdateInput,
)
# Tool
from agentrun.tool import Tool as ToolResource
from agentrun.tool import ToolClient as ToolResourceClient
Expand Down Expand Up @@ -248,6 +263,20 @@
"ModelProxyCreateInput",
"ModelProxyUpdateInput",
"ModelProxyListInput",
######## Super Agent ########
# base
"SuperAgent",
"SuperAgentClient",
# inner model
"InvokeStream",
"SSEEvent",
"ConversationInfo",
"SuperAgentMessage",
# api model
"SuperAgentCreateInput",
"SuperAgentUpdateInput",
"SuperAgentListInput",
"InvokeResponseData",
######## Sandbox ########
"SandboxClient",
"BrowserSandbox",
Expand Down
7 changes: 6 additions & 1 deletion agentrun/agent_runtime/__client_async_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,12 @@ async def delete_async(
result = await self.__control_api.delete_agent_runtime_async(
id, config=config
)
return AgentRuntime.from_inner_object(result)
# Delete 响应只有 agentRuntimeId 有效,其他字段为空字符串/零值,
# 走 from_inner_object 会在 status/artifactType 等 Enum 字段上触发
# 伪校验错误。这里直接构造一个只带 id 的极简对象。
return AgentRuntime.model_construct(
agent_runtime_id=result.agent_runtime_id
)
except HTTPError as e:
raise e.to_resource_error("AgentRuntime", id) from e

Expand Down
14 changes: 12 additions & 2 deletions agentrun/agent_runtime/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,12 @@ async def delete_async(
result = await self.__control_api.delete_agent_runtime_async(
id, config=config
)
return AgentRuntime.from_inner_object(result)
# Delete 响应只有 agentRuntimeId 有效,其他字段为空字符串/零值,
# 走 from_inner_object 会在 status/artifactType 等 Enum 字段上触发
# 伪校验错误。这里直接构造一个只带 id 的极简对象。
return AgentRuntime.model_construct(
agent_runtime_id=result.agent_runtime_id
)
except HTTPError as e:
raise e.to_resource_error("AgentRuntime", id) from e

Expand All @@ -191,7 +196,12 @@ def delete(self, id: str, config: Optional[Config] = None) -> AgentRuntime:
"""
try:
result = self.__control_api.delete_agent_runtime(id, config=config)
return AgentRuntime.from_inner_object(result)
# Delete 响应只有 agentRuntimeId 有效,其他字段为空字符串/零值,
# 走 from_inner_object 会在 status/artifactType 等 Enum 字段上触发
# 伪校验错误。这里直接构造一个只带 id 的极简对象。
return AgentRuntime.model_construct(
agent_runtime_id=result.agent_runtime_id
)
except HTTPError as e:
raise e.to_resource_error("AgentRuntime", id) from e

Expand Down
1 change: 1 addition & 0 deletions agentrun/agent_runtime/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ class AgentRuntimeProtocolType(str, Enum):

HTTP = "HTTP"
MCP = "MCP"
SUPER_AGENT = "SUPER_AGENT"


class AgentRuntimeProtocolConfig(BaseModel):
Expand Down
4 changes: 2 additions & 2 deletions agentrun/integration/utils/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -1614,8 +1614,8 @@ def _build_openapi_schema(
if isinstance(schema, dict):
properties[name] = {
**schema,
"description": (
param.get("description") or schema.get("description", "")
"description": param.get("description") or schema.get(
"description", ""
),
}
if param.get("required"):
Expand Down
195 changes: 195 additions & 0 deletions agentrun/super_agent/__agent_async_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
"""SuperAgent 实例 / Super Agent Instance

``SuperAgent`` 是暴露给应用开发者的强类型实例对象, 承载 ``invoke`` / 会话管理
两类方法 (仅异步; 见决策 14)。CRUDL 由 ``SuperAgentClient`` 管理。

本文件为模板 (``__agent_async_template.py``), codegen 会把 ``async def ...``
转换成同步骨架; 实际第一版异步主路径 + 同步 NotImplementedError 占位。
"""

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

from agentrun.super_agent.api.data import SuperAgentDataAPI
from agentrun.super_agent.model import ConversationInfo, Message
from agentrun.super_agent.stream import InvokeStream
from agentrun.utils.config import Config

_SYNC_UNSUPPORTED_MSG = (
"sync version not supported, use *_async (see decision 14 in"
" openspec/changes/add-super-agent-sdk/design.md)"
)


@dataclass
class SuperAgent:
"""超级 Agent 实例.

业务字段 (``prompt / agents / tools / ...``) 从 ``protocolSettings.config``
反解。系统字段 (``agent_runtime_id / arn / status / ...``) 来自 AgentRuntime。
"""

name: str
description: Optional[str] = None
prompt: Optional[str] = None
agents: List[str] = field(default_factory=list)
tools: List[str] = field(default_factory=list)
skills: List[str] = field(default_factory=list)
sandboxes: List[str] = field(default_factory=list)
workspaces: List[str] = field(default_factory=list)
model_service_name: Optional[str] = None
model_name: Optional[str] = None

agent_runtime_id: str = ""
arn: str = ""
status: str = ""
created_at: str = ""
last_updated_at: str = ""
external_endpoint: str = ""

_client: Any = field(default=None, repr=False, compare=False)

def _resolve_config(self, config: Optional[Config]) -> Config:
client_cfg = (
getattr(self._client, "config", None) if self._client else None
)
return Config.with_configs(client_cfg, config)

def _forwarded_business_fields(self) -> Dict[str, Any]:
"""把 SuperAgent 实例字段打包成 ``forwardedProps`` 顶层业务字段 dict.

与 ``protocolSettings[0].config`` 写入时的结构保持对称: list 型用 ``[]``
代替 None, scalar 型保留 None (由 JSON 序列化为 ``null``)。服务端读取同
一份语义, 避免客户端/服务端对"未设置"产生歧义。
"""
return {
"prompt": self.prompt,
"agents": list(self.agents),
"tools": list(self.tools),
"skills": list(self.skills),
"sandboxes": list(self.sandboxes),
"workspaces": list(self.workspaces),
"modelServiceName": self.model_service_name,
"modelName": self.model_name,
}

async def invoke_async(
self,
messages: List[Dict[str, Any]],
*,
conversation_id: Optional[str] = None,
config: Optional[Config] = None,
) -> InvokeStream:
"""Phase 1: POST /invoke; 返回包含 ``conversation_id`` 的 :class:`InvokeStream`.

首次 ``async for ev in stream`` 才触发 Phase 2 拉流 (lazy)。
"""
cfg = self._resolve_config(config)
api = SuperAgentDataAPI(self.name, config=cfg)
resp = await api.invoke_async(
messages,
conversation_id=conversation_id,
config=cfg,
forwarded_extras=self._forwarded_business_fields(),
)
stream_url = resp.stream_url
stream_headers = dict(resp.stream_headers)
session_id = stream_headers.get("X-Super-Agent-Session-Id", "")

async def _factory():
return api.stream_async(
stream_url, stream_headers=stream_headers, config=cfg
)

return InvokeStream(
conversation_id=resp.conversation_id,
session_id=session_id,
stream_url=stream_url,
stream_headers=stream_headers,
_stream_factory=_factory,
)

def invoke(
self,
messages: List[Dict[str, Any]],
*,
conversation_id: Optional[str] = None,
config: Optional[Config] = None,
) -> InvokeStream:
raise NotImplementedError(_SYNC_UNSUPPORTED_MSG)

async def get_conversation_async(
self,
conversation_id: str,
*,
config: Optional[Config] = None,
) -> ConversationInfo:
"""GET /conversations/{id} → :class:`ConversationInfo` (缺字段用默认值)."""
cfg = self._resolve_config(config)
api = SuperAgentDataAPI(self.name, config=cfg)
data = await api.get_conversation_async(conversation_id, config=cfg)
return _conversation_info_from_dict(
data, fallback_conversation_id=conversation_id
)

def get_conversation(
self,
conversation_id: str,
*,
config: Optional[Config] = None,
) -> ConversationInfo:
raise NotImplementedError(_SYNC_UNSUPPORTED_MSG)

async def delete_conversation_async(
self,
conversation_id: str,
*,
config: Optional[Config] = None,
) -> None:
"""DELETE /conversations/{id}."""
cfg = self._resolve_config(config)
api = SuperAgentDataAPI(self.name, config=cfg)
await api.delete_conversation_async(conversation_id, config=cfg)

def delete_conversation(
self,
conversation_id: str,
*,
config: Optional[Config] = None,
) -> None:
raise NotImplementedError(_SYNC_UNSUPPORTED_MSG)


def _to_message(raw: Dict[str, Any]) -> Message:
return Message(
role=str(raw.get("role") or ""),
content=str(raw.get("content") or ""),
message_id=raw.get("messageId") or raw.get("message_id"),
created_at=raw.get("createdAt") or raw.get("created_at"),
)


def _conversation_info_from_dict(
data: Dict[str, Any], *, fallback_conversation_id: str
) -> ConversationInfo:
data = data or {}
messages_raw = data.get("messages") or []
messages = [_to_message(m) for m in messages_raw if isinstance(m, dict)]
return ConversationInfo(
conversation_id=str(
data.get("conversationId") or fallback_conversation_id
),
agent_id=str(data.get("agentId") or data.get("agent_id") or ""),
title=data.get("title"),
main_user_id=data.get("mainUserId") or data.get("main_user_id"),
sub_user_id=data.get("subUserId") or data.get("sub_user_id"),
created_at=int(data.get("createdAt") or data.get("created_at") or 0),
updated_at=int(data.get("updatedAt") or data.get("updated_at") or 0),
error_message=data.get("errorMessage") or data.get("error_message"),
invoke_info=data.get("invokeInfo") or data.get("invoke_info"),
messages=messages,
params=data.get("params"),
)


__all__ = ["SuperAgent"]
Loading
Loading