diff --git a/veadk/agent.py b/veadk/agent.py index 3bdce870..92e1a177 100644 --- a/veadk/agent.py +++ b/veadk/agent.py @@ -309,13 +309,13 @@ def load_skills(self): load_skills_from_cloud, load_skills_from_directory, ) - from veadk.tools.builtin_tools.playwright import playwright_tools from veadk.tools.skills_tools import ( SkillsTool, bash_tool, edit_file_tool, read_file_tool, write_file_tool, + register_skills_tool, ) skills: Dict[str, Skill] = {} @@ -343,7 +343,7 @@ def load_skills(self): self.tools.append(write_file_tool) self.tools.append(edit_file_tool) self.tools.append(bash_tool) - self.tools.append(playwright_tools) + self.tools.append(register_skills_tool) def _prepare_tracers(self): enable_apmplus_tracer = os.getenv("ENABLE_APMPLUS", "false").lower() == "true" diff --git a/veadk/tools/builtin_tools/execute_skills.py b/veadk/tools/builtin_tools/execute_skills.py index 95bb9142..7a3b0e5f 100644 --- a/veadk/tools/builtin_tools/execute_skills.py +++ b/veadk/tools/builtin_tools/execute_skills.py @@ -14,7 +14,6 @@ import json import os -from typing import Optional, List from google.adk.tools import ToolContext @@ -69,7 +68,6 @@ def _format_execution_result(result_str: str) -> str: def execute_skills( workflow_prompt: str, - skills: Optional[List[str]] = None, tool_context: ToolContext = None, ) -> str: """execute skills in a code sandbox and return the output. @@ -77,7 +75,6 @@ def execute_skills( Args: workflow_prompt (str): instruction of workflow - skills (Optional[List[str]]): The skills will be invoked Returns: str: The output of the code execution. @@ -127,8 +124,6 @@ def execute_skills( logger.debug("Successfully get AK/SK from tool context.") cmd = ["python", "agent.py", workflow_prompt] - if skills: - cmd.extend(["--skills"] + skills) skill_space_id = os.getenv("SKILL_SPACE_ID", "") if not skill_space_id: diff --git a/veadk/tools/skills_tools/__init__.py b/veadk/tools/skills_tools/__init__.py index b44d44f8..47679a29 100644 --- a/veadk/tools/skills_tools/__init__.py +++ b/veadk/tools/skills_tools/__init__.py @@ -15,6 +15,7 @@ from .bash_tool import bash_tool from .file_tool import edit_file_tool, read_file_tool, write_file_tool from .skills_tool import SkillsTool +from .register_skills_tool import register_skills_tool from .session_path import initialize_session_path, get_session_path, clear_session_cache @@ -23,6 +24,7 @@ "edit_file_tool", "read_file_tool", "write_file_tool", + "register_skills_tool", "SkillsTool", "initialize_session_path", "get_session_path", diff --git a/veadk/tools/skills_tools/register_skills_tool.py b/veadk/tools/skills_tools/register_skills_tool.py new file mode 100644 index 00000000..22ba926e --- /dev/null +++ b/veadk/tools/skills_tools/register_skills_tool.py @@ -0,0 +1,176 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import json +import os +import zipfile +from pathlib import Path +from datetime import datetime + +from google.adk.tools import ToolContext + +from veadk.tools.skills_tools.session_path import get_session_path +from veadk.integrations.ve_tos.ve_tos import VeTOS +from veadk.utils.volcengine_sign import ve_request +from veadk.utils.logger import get_logger + +logger = get_logger(__name__) + + +def register_skills_tool( + skill_name: str, + skill_description: str, + skill_local_path: str, + tool_context: ToolContext, +) -> str: + """Register a skill to the remote skill space by uploading its zip package to TOS and calling the CreateSkill API. + + Args: + skill_name (str): The name of the skill. + skill_description (str): The description of the skill. + skill_local_path (str): The local path of the skill directory. + - The format of the skill directory is as follows: + skill_local_path/ + SKILL.md + other files... + tool_context (ToolContext): The context of the tool execution. + + Returns: + str: Result message indicating success or failure. + """ + working_dir = get_session_path(session_id=tool_context.session.id) + + # skill_path = Path(skill_local_path) + raw = Path(skill_local_path).expanduser() + if not raw.is_absolute(): + skill_path = (working_dir / raw).resolve() + else: + skill_path = raw.resolve() + if not skill_path.exists() or not skill_path.is_dir(): + logger.error(f"Skill path '{skill_path}' does not exist or is not a directory.") + return f"Skill path '{skill_path}' does not exist or is not a directory." + + skill_readme = skill_path / "SKILL.md" + if not skill_readme.exists(): + logger.error(f"Skill path '{skill_path}' has no SKILL.md file.") + return f"Skill path '{skill_path}' has no SKILL.md file." + + zip_file_path = working_dir / "outputs" / f"{skill_name}.zip" + + with zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk(skill_path): + for file in files: + file_path = Path(root) / file + arcname = Path(skill_name) / file_path.relative_to(skill_path) + zipf.write(file_path, arcname) + + try: + from veadk.auth.veauth.utils import get_credential_from_vefaas_iam + + service = os.getenv("AGENTKIT_TOOL_SERVICE_CODE", "agentkit") + region = os.getenv("AGENTKIT_TOOL_REGION", "cn-beijing") + host = os.getenv("AGENTKIT_SKILL_HOST", "open.volcengineapi.com") + + access_key = os.getenv("VOLCENGINE_ACCESS_KEY") + secret_key = os.getenv("VOLCENGINE_SECRET_KEY") + session_token = "" + region = os.getenv("AGENTKIT_TOOL_REGION", "cn-beijing") + + if not (access_key and secret_key): + cred = get_credential_from_vefaas_iam() + access_key = cred.access_key_id + secret_key = cred.secret_access_key + session_token = cred.session_token + + res = ve_request( + request_body={}, + action="GetCallerIdentity", + ak=access_key, + sk=secret_key, + service="sts", + version="2018-01-01", + region=region, + host="sts.volcengineapi.com", + header={"X-Security-Token": session_token}, + ) + try: + account_id = res["Result"]["AccountId"] + except KeyError as e: + logger.error( + f"Error occurred while getting account id: {e}, response is {res}" + ) + return f"Error: Failed to get account id when registering skill '{skill_name}'." + + tos_bucket = f"agentkit-platform-{region}-{account_id}-skill" + + tos_client = VeTOS( + ak=access_key, + sk=secret_key, + session_token=session_token, + bucket_name=tos_bucket, + ) + + object_key = ( + f"uploads/{datetime.now().strftime('%Y%m%d_%H%M%S')}/{skill_name}.zip" + ) + tos_client.upload_file( + file_path=zip_file_path, bucket_name=tos_bucket, object_key=object_key + ) + tos_url = tos_client.build_tos_url( + bucket_name=tos_bucket, object_key=object_key + ) + + skill_space_ids = os.getenv("SKILL_SPACE_ID", "") + skill_space_ids_list = [ + x.strip() for x in skill_space_ids.split(",") if x.strip() + ] + + response = ve_request( + request_body={ + "Name": skill_name, + "Description": skill_description, + "TosUrl": tos_url, + "SkillSpaces": skill_space_ids_list, + }, + action="CreateSkill", + ak=access_key, + sk=secret_key, + service=service, + version="2025-10-30", + region=region, + host=host, + header={"X-Security-Token": session_token}, + ) + + if isinstance(response, str): + response = json.loads(response) + + logger.debug(f"CreateSkill response: {response}") + + if "Error" in response: + logger.error( + f"Failed to register skill '{skill_name}': {response['Error']}" + ) + return f"Failed to register skill '{skill_name}': {response['Error']}" + + logger.info( + f"Successfully registered skill '{skill_name}' to skill space {skill_space_ids_list}." + ) + return f"Successfully registered skill '{skill_name}' to skill space {skill_space_ids_list}." + + except Exception as e: + logger.error(f"Failed to register skill '{skill_name}': {e}") + return f"Failed to register skill '{skill_name}'" diff --git a/veadk/tools/skills_tools/session_path.py b/veadk/tools/skills_tools/session_path.py index 2e985d41..66e7ebf4 100644 --- a/veadk/tools/skills_tools/session_path.py +++ b/veadk/tools/skills_tools/session_path.py @@ -92,7 +92,7 @@ def get_session_path(session_id: str) -> Path: f"Auto-initializing with default /skills. " f"Install SkillsPlugin for custom skills directories." ) - return initialize_session_path(session_id, "/skills") + return initialize_session_path(session_id) def clear_session_cache(session_id: str | None = None) -> None: