Files
SergeyMenshykh 7432105ebe Python: Support list[str] arguments for file-based skill scripts (#5850)
Port of .NET PR #5475. Broadens the args type from dict[str, Any] | None
to dict[str, Any] | list[str] | None across the skill script API surface,
enabling CLI-style argv forwarding to subprocess scripts.

Changes:
- SkillScript.run(), InlineSkillScript.run(), FileSkillScript.run(): widen
  args type; InlineSkillScript rejects list with TypeError
- FileSkillScript.parameters_schema: returns array-of-strings schema
- FileSkill.content: appends <scripts> block with parameters_schema
- SkillScriptRunner protocol: widen args type
- SkillsProvider._run_skill_script: widen args type
- run_skill_script tool schema: accept object, array, or null
- subprocess_script_runner sample: accept list[str], reject dict
- class_based_skill sample: fix missing SkillFrontmatter wrapper
- Standardize 'folder' to 'directory' in docstrings (#5712)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-14 17:58:10 +00:00

78 lines
2.9 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
"""Sample subprocess-based skill script runner.
Executes file-based skill scripts as local Python subprocesses.
This is provided for demonstration purposes only.
"""
from __future__ import annotations
import subprocess
import sys
# Uncomment this filter to suppress the experimental Skills warning before
# using the sample's Skills APIs.
# import warnings
# warnings.filterwarnings("ignore", message=r"\[SKILLS\].*", category=FutureWarning)
from pathlib import Path
from typing import Any
from agent_framework import FileSkill, FileSkillScript
def subprocess_script_runner(
skill: FileSkill, script: FileSkillScript, args: dict[str, Any] | list[str] | None = None
) -> str:
"""Run a skill script as a local Python subprocess.
Uses ``FileSkillScript.full_path`` as the script path, converts the
``args`` to CLI arguments, and returns captured output.
Args:
skill: The file-based skill that owns the script.
script: The file-based script to run.
args: Optional arguments. A ``list[str]`` is forwarded as
positional CLI arguments. Passing a ``dict`` or any other
type raises :class:`TypeError` — file-based scripts expect
positional arguments as a JSON array of strings.
Returns:
The combined stdout/stderr output, or an error message.
Raises:
TypeError: If ``args`` is not a ``list[str]`` or ``None``, or if
any list element is not a string.
"""
script_path = Path(script.full_path)
if not script_path.is_file():
return f"Error: Script file not found: {script_path}"
cmd = [sys.executable, str(script_path)]
if isinstance(args, list):
for item in args:
if not isinstance(item, str):
raise TypeError(
f"File-based skill scripts only accept string CLI arguments "
f"but received a {type(item).__name__}. "
f"All array elements must be strings."
)
cmd.extend(args)
elif args is not None:
raise TypeError(
f"Expected a list of CLI arguments but received {type(args).__name__}. "
f"File-based skill scripts expect positional arguments as a list of strings."
)
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30,
cwd=str(script_path.parent),
)
output = result.stdout
if result.stderr:
output += f"\nStderr:\n{result.stderr}"
if result.returncode != 0:
output += f"\nScript exited with code {result.returncode}"
return output.strip() or "(no output)"
except subprocess.TimeoutExpired:
return f"Error: Script '{script.name}' timed out after 30 seconds."
except OSError as e:
return f"Error: Failed to execute script '{script.name}': {e}"