Skip to content

Commit b9d9c6f

Browse files
committed
refactor(tool): 增加新版工具ram链路鉴权支持
添加了ToolCreateMethod枚举类,定义了多种工具创建和部署方式,并相应地更新了相关的导入和导出语句。同时增加了对异步事件循环处理的单元测试。 Co-developed-by: Aone Copilot <noreply@alibaba-inc.com> Signed-off-by: Sodawyx <sodawyx@126.com>
1 parent 2757f2f commit b9d9c6f

File tree

9 files changed

+1361
-14
lines changed

9 files changed

+1361
-14
lines changed

agentrun/tool/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
McpConfig,
1313
ToolCodeConfiguration,
1414
ToolContainerConfiguration,
15+
ToolCreateMethod,
1516
ToolInfo,
1617
ToolLogConfiguration,
1718
ToolNASConfig,
@@ -29,6 +30,7 @@
2930
"ToolClient",
3031
"Tool",
3132
"ToolType",
33+
"ToolCreateMethod",
3234
"McpConfig",
3335
"ToolCodeConfiguration",
3436
"ToolContainerConfiguration",

agentrun/tool/__tool_async_template.py

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import shutil
1010
from typing import Any, Dict, List, Optional
11+
from urllib.parse import urlparse
1112
import zipfile
1213

1314
import httpx
@@ -16,6 +17,7 @@
1617
from agentrun.utils.config import Config
1718
from agentrun.utils.log import logger
1819
from agentrun.utils.model import BaseModel
20+
from agentrun.utils.ram_signature import get_agentrun_signed_headers
1921

2022
from .model import (
2123
McpConfig,
@@ -128,9 +130,20 @@ class Tool(BaseModel):
128130
tool_type: Optional[str] = None
129131
"""工具类型(MCP/FUNCTIONCALL) / Tool type (MCP/FUNCTIONCALL)"""
130132

133+
create_method: Optional[str] = None
134+
"""工具创建方式 / Tool create method
135+
MCP_REMOTE: 远程 MCP 服务器 / Remote MCP server
136+
MCP_LOCAL: 本地 MCP 标准输入输出 / Local MCP stdio
137+
MCP_BUNDLE: MCP 打包部署 / MCP bundle deployment
138+
CODE_PACKAGE: 代码包部署 / Code package deployment
139+
OPENAPI_IMPORT: OpenAPI 导入 / OpenAPI import
140+
"""
141+
131142
version_id: Optional[str] = None
132143
"""版本 ID / Version ID"""
133144

145+
_RAM_DATA_DOMAINS = ("agentrun-data", "funagent-data-pre")
146+
134147
@classmethod
135148
def __get_client(cls, config: Optional[Config] = None):
136149
from .client import ToolClient
@@ -238,20 +251,36 @@ async def list_tools_async(
238251
self, "mcp_config.session_affinity", "MCP_SSE"
239252
)
240253

254+
# MCP_REMOTE + proxy_enabled=false 时直连外部服务,不走 RAM 鉴权
255+
# Only skip RAM auth for MCP_REMOTE with proxy disabled (direct external connection)
256+
is_mcp_remote_without_proxy = (
257+
self.create_method == "MCP_REMOTE"
258+
and not pydash.get(self, "mcp_config.proxy_enabled", False)
259+
)
260+
241261
cfg = Config.with_configs(config)
242262
session = ToolMCPSession(
243263
endpoint=mcp_endpoint,
244264
session_affinity=session_affinity,
245265
headers=cfg.get_headers(),
266+
config=cfg,
267+
use_ram_auth=not is_mcp_remote_without_proxy,
246268
)
247269
return await session.list_tools_async()
248270

249271
elif tool_type == ToolType.FUNCTIONCALL:
250272
from .api.openapi import ToolOpenAPIClient
251273

274+
# OPENAPI_IMPORT 时 server 是外部服务,不走 RAM 鉴权
275+
# Skip RAM auth for OPENAPI_IMPORT since the server is an external service
276+
is_openapi_import = self.create_method == "OPENAPI_IMPORT"
277+
278+
cfg = Config.with_configs(config)
252279
openapi_client = ToolOpenAPIClient(
253280
protocol_spec=self.protocol_spec,
254281
fallback_server_url=self._get_functioncall_server_url(config),
282+
config=cfg,
283+
use_ram_auth=not is_openapi_import,
255284
)
256285
return await openapi_client.list_tools_async()
257286

@@ -290,11 +319,20 @@ async def call_tool_async(
290319
self, "mcp_config.session_affinity", "MCP_SSE"
291320
)
292321

322+
# MCP_REMOTE + proxy_enabled=false 时直连外部服务,不走 RAM 鉴权
323+
# Only skip RAM auth for MCP_REMOTE with proxy disabled (direct external connection)
324+
is_mcp_remote_without_proxy = (
325+
self.create_method == "MCP_REMOTE"
326+
and not pydash.get(self, "mcp_config.proxy_enabled", False)
327+
)
328+
293329
cfg = Config.with_configs(config)
294330
session = ToolMCPSession(
295331
endpoint=mcp_endpoint,
296332
session_affinity=session_affinity,
297333
headers=cfg.get_headers(),
334+
config=cfg,
335+
use_ram_auth=not is_mcp_remote_without_proxy,
298336
)
299337
result = await session.call_tool_async(name, arguments)
300338
logger.debug("invoke tool %s got result %s", name, result)
@@ -303,18 +341,101 @@ async def call_tool_async(
303341
elif tool_type == ToolType.FUNCTIONCALL:
304342
from .api.openapi import ToolOpenAPIClient
305343

344+
# OPENAPI_IMPORT 时 server 是外部服务,不走 RAM 鉴权
345+
# Skip RAM auth for OPENAPI_IMPORT since the server is an external service
346+
is_openapi_import = self.create_method == "OPENAPI_IMPORT"
347+
306348
cfg = Config.with_configs(config)
307349
openapi_client = ToolOpenAPIClient(
308350
protocol_spec=self.protocol_spec,
309351
headers=cfg.get_headers(),
310352
fallback_server_url=self._get_functioncall_server_url(config),
353+
config=cfg,
354+
use_ram_auth=not is_openapi_import,
311355
)
312356
result = await openapi_client.call_tool_async(name, arguments)
313357
logger.debug("invoke tool %s got result %s", name, result)
314358
return result
315359

316360
raise ValueError(f"Unsupported tool type: {self.tool_type}")
317361

362+
def _use_ram_auth(self, config: Optional[Config] = None) -> bool:
363+
"""是否使用 RAM 签名鉴权(配置了 AK/SK 时使用)。
364+
Whether to use RAM signature authentication (when AK/SK is configured).
365+
"""
366+
cfg = Config.with_configs(config)
367+
return bool(cfg.get_access_key_id() and cfg.get_access_key_secret())
368+
369+
def _get_ram_data_endpoint(
370+
self, url: str, config: Optional[Config] = None
371+
) -> str:
372+
"""返回 RAM 鉴权用的 data endpoint(仅当 agentrun-data / funagent-data-pre 域名时在 host 前加 -ram)。
373+
Return RAM-authenticated endpoint (add -ram prefix for agentrun-data / funagent-data-pre domains).
374+
"""
375+
parsed = urlparse(url)
376+
if not parsed.netloc or not any(
377+
f".{domain}." in parsed.netloc for domain in self._RAM_DATA_DOMAINS
378+
):
379+
return url
380+
parts = parsed.netloc.split(".", 1)
381+
if len(parts) != 2:
382+
return url
383+
ram_netloc = parts[0] + "-ram." + parts[1]
384+
385+
from urllib.parse import urlunparse
386+
387+
return urlunparse((
388+
parsed.scheme,
389+
ram_netloc,
390+
parsed.path,
391+
parsed.params,
392+
parsed.query,
393+
parsed.fragment,
394+
))
395+
396+
def _get_auth_headers(
397+
self, url: str, config: Optional[Config] = None
398+
) -> Dict[str, str]:
399+
"""获取认证请求头,支持 RAM 签名。
400+
Get authentication headers with RAM signature support.
401+
402+
Args:
403+
url: 请求 URL / Request URL
404+
config: 配置对象 / Configuration object
405+
406+
Returns:
407+
Dict[str, str]: 包含认证信息的请求头 / Headers with authentication
408+
"""
409+
cfg = Config.with_configs(config)
410+
headers = cfg.get_headers()
411+
412+
if self._use_ram_auth(cfg):
413+
# 使用 RAM 端点
414+
ram_url = self._get_ram_data_endpoint(url, cfg)
415+
try:
416+
signed = get_agentrun_signed_headers(
417+
url=ram_url,
418+
method="GET",
419+
access_key_id=cfg.get_access_key_id(),
420+
access_key_secret=cfg.get_access_key_secret(),
421+
security_token=cfg.get_security_token() or None,
422+
region=cfg.get_region_id(),
423+
product="agentrun",
424+
body=None,
425+
)
426+
headers = {
427+
**signed,
428+
**headers,
429+
}
430+
logger.debug(
431+
"using RAM signature for skill download to %s",
432+
ram_url[:80] + "..." if len(ram_url) > 80 else ram_url,
433+
)
434+
except ValueError as e:
435+
logger.warning("RAM signing skipped (missing AK/SK): %s", e)
436+
437+
return headers
438+
318439
def _get_skill_download_url(
319440
self, config: Optional[Config] = None
320441
) -> Optional[str]:
@@ -376,7 +497,7 @@ async def download_skill_async(
376497
logger.debug("downloading skill from %s to %s", download_url, skill_dir)
377498

378499
cfg = Config.with_configs(config)
379-
headers = cfg.get_headers()
500+
headers = self._get_auth_headers(download_url, cfg)
380501

381502
async with httpx.AsyncClient(
382503
timeout=300, follow_redirects=True

0 commit comments

Comments
 (0)