Imported from kody-w/rapp-monorepo (
repos/RAR/scout/bundles/native-01/skills/rar-discreetrappers-agent-transpiler/SKILL.md). Install upstream withnpx skills add kody-w/rapp-monorepo --skill rar-discreetrappers-agent-transpiler. Copyright stays with the author.
Microsoft Scout runtime
This is the reversible Scout projection of @discreetRappers/agent_transpiler_agent. The original RAPP
agent is preserved byte-for-byte in agent_transpiler_agent.py and in the RCI capsule.
When Scout can execute local files, resolve this skill directory and run:
python3 scripts/run_agent.py --preflight
echo '{}' | python3 scripts/run_agent.py
Pass the real JSON arguments instead of {}. The runner verifies the linked
agent SHA-256 before importing it. If preflight reports a host dependency that
Scout cannot satisfy, use the brainstem_chat MCP tool to run the canonical
agent in the user's Brainstem. Never paraphrase the factory or agent into a new
implementation. The generic direct-file commands in the generated Toaster
section are recovery guidance; Scout should prefer the verified runner.
Agent Transpiler - Multi-Platform Agent Factory
Converts RAPP agent definitions to multiple target platforms:
- M365 Copilot Declarative Agents
- Copilot Studio Agents
- Azure AI Foundry Agents
This enables RAPP to be a universal agent builder that can deploy to any platform.
Usage: transpiler = AgentTranspilerAgent() result = transpiler.perform( action="transpile", agent_name="FabrikamCaseTriageOrchestrator", target_platform="copilot_studio" )
Parameters
The typed contract this capability answers to (JSON Schema — the deterministic layer):
{
"properties": {
"action": {
"description": "The transpilation action to perform",
"enum": [
"transpile",
"analyze",
"generate_openapi",
"preview",
"list_platforms",
"batch_transpile"
],
"type": "string"
},
"agent_json": {
"description": "Optional: Direct agent JSON instead of loading by name",
"type": "object"
},
"agent_name": {
"description": "Name of the RAPP agent to transpile",
"type": "string"
},
"function_app_url": {
"description": "URL of the RAPP Function App for API connections",
"type": "string"
},
"output_path": {
"description": "Path to save generated files",
"type": "string"
},
"save_files": {
"default": false,
"description": "Whether to save generated files to disk",
"type": "boolean"
},
"target_platform": {
"description": "Target platform for transpilation",
"enum": [
"m365_copilot",
"copilot_studio",
"azure_foundry",
"all"
],
"type": "string"
}
},
"required": [
"action"
],
"type": "object"
}
Run this — do not improvise
This capability's deterministic implementation is a RAPP single-file agent, linked beside this file as agent_transpiler_agent.py and embedded as the fenced Python below (sha256 5ae4e24760415ab5…; a byte-exact copy is also vaulted in the capsule comment at the end of this file). On a host with sandbox execution, run the linked file directly — if it is missing, write the fence contents verbatim to agent_transpiler_agent.py first:
python3 agent_transpiler_agent.py '{"key": "value"}' # arguments as one JSON object
echo '{"key": "value"}' | python3 agent_transpiler_agent.py # or on stdin
python3 agent_transpiler_agent.py --tool # emit the JSON tool contract
Treat stdout as a tool result. If it reports missing or unresolved inputs, stop and collect them. If it returns steps, execute those steps in order exactly as returned; if it returns instructions, follow them with the supplied inputs. Otherwise use the result verbatim. Do not invent behavior beyond that output. On a host without code execution, treat the Parameters schema and the code below as the exact specification and never paraphrase a step. Never edit inside the generated markers; a converter-equipped host can instead restore the original file checksum-verified with the installed rapp-agent-converter/scripts/toast.py convert SKILL.md --to agent.
"""
Agent Transpiler - Multi-Platform Agent Factory
Converts RAPP agent definitions to multiple target platforms:
1. M365 Copilot Declarative Agents
2. Copilot Studio Agents
3. Azure AI Foundry Agents
This enables RAPP to be a universal agent builder that can deploy to any platform.
Usage:
transpiler = AgentTranspilerAgent()
result = transpiler.perform(
action="transpile",
agent_name="FabrikamCaseTriageOrchestrator",
target_platform="copilot_studio"
)
"""
# ═══════════════════════════════════════════════════════════════
# RAPP AGENT MANIFEST — Do not remove. Used by registry builder.
# ═══════════════════════════════════════════════════════════════
__manifest__ = {
"schema": "rapp-agent/1.0",
"name": "@discreetRappers/agent_transpiler_agent",
"version": "1.0.1",
"display_name": "AgentTranspiler",
"description": "Converts RAPP agent definitions into M365 declarative, Copilot Studio, and Azure AI Foundry formats, with optional Foundry deployment.",
"author": "Bill Whalen",
"tags": ["pipeline", "transpiler", "m365", "copilot-studio", "multi-platform"],
"category": "pipeline",
"quality_tier": "community",
"requires_env": ["AI_PROJECT_CONNECTION_STRING"],
"dependencies": ["@rapp/basic_agent"],
}
# ═══════════════════════════════════════════════════════════════
import json
import os
import re
import logging
from datetime import datetime
from typing import Optional, List, Dict, Any
from agents.basic_agent import BasicAgent
logger = logging.getLogger(__name__)
# =============================================================================
# PLATFORM CONFIGURATIONS
# =============================================================================
SUPPORTED_PLATFORMS = {
"m365_copilot": {
"name": "M365 Copilot Declarative Agent",
"description": "Declarative agents for Microsoft 365 Copilot with API plugins",
"output_files": ["declarativeAgent.json", "plugin.json", "openapi.yaml"],
"best_for": ["Teams integration", "Outlook integration", "SharePoint integration"]
},
"copilot_studio": {
"name": "Copilot Studio Agent",
"description": "Low-code agents with Power Platform connectors",
"output_files": ["agent.yaml", "topics/*.yaml", "connector.json"],
"best_for": ["Power Platform", "Low-code", "Business users"]
},
"azure_foundry": {
"name": "Azure AI Foundry Agent",
"description": "Full Python agents with Azure AI Agent Service",
"output_files": ["agent.py", "tools.py", "config.yaml"],
"best_for": ["Complex logic", "Custom integrations", "Full control"]
}
}
# M365 Copilot manifest version
M365_MANIFEST_VERSION = "v1.6"
# =============================================================================
# AGENT TRANSPILER
# =============================================================================
class AgentTranspilerAgent(BasicAgent):
"""
Multi-Platform Agent Factory - Transpiles RAPP agents to various platforms.
Capabilities:
- transpile: Convert agent to target platform format
- analyze: Recommend best platform for an agent
- generate_openapi: Create OpenAPI spec for RAPP Function App
- preview: Show what would be generated without saving
- list_platforms: Show supported target platforms
"""
def __init__(self):
self.name = "AgentTranspiler"
self.metadata = {
"name": self.name,
"description": "Converts RAPP agent definitions to M365 Copilot, Copilot Studio, or Azure AI Foundry formats.",
"parameters": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"transpile",
"analyze",
"generate_openapi",
"preview",
"list_platforms",
"batch_transpile"
],
"description": "The transpilation action to perform"
},
"agent_name": {
"type": "string",
"description": "Name of the RAPP agent to transpile"
},
"target_platform": {
"type": "string",
"enum": ["m365_copilot", "copilot_studio", "azure_foundry", "all"],
"description": "Target platform for transpilation"
},
"agent_json": {
"type": "object",
"description": "Optional: Direct agent JSON instead of loading by name"
},
"function_app_url": {
"type": "string",
"description": "URL of the RAPP Function App for API connections"
},
"save_files": {
"type": "boolean",
"description": "Whether to save generated files to disk",
"default": False
},
"output_path": {
"type": "string",
"description": "Path to save generated files"
}
},
"required": ["action"]
}
}
super().__init__(name=self.name, metadata=self.metadata)
# Paths
self.base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.demos_path = os.path.join(self.base_path, "demos")
self.agents_path = os.path.join(self.base_path, "agents")
self.output_path = os.path.join(self.base_path, "transpiled")
def perform(self, **kwargs) -> str:
"""Route to appropriate action handler."""
action = kwargs.get("action", "list_platforms")
actions = {
"transpile": self._transpile,
"analyze": self._analyze,
"generate_openapi": self._generate_openapi,
"preview": self._preview,
"list_platforms": self._list_platforms,
"batch_transpile": self._batch_transpile,
}
if action not in actions:
return json.dumps({
"status": "error",
"error": f"Unknown action: {action}",
"available_actions": list(actions.keys())
})
try:
return actions[action](**kwargs)
except Exception as e:
logger.error(f"Error in AgentTranspiler.{action}: {e}")
return json.dumps({
"status": "error",
"error": str(e)
})
# =========================================================================
# ACTION HANDLERS
# =========================================================================
def _list_platforms(self, **kwargs) -> str:
"""List all supported target platforms."""
return json.dumps({
"status": "success",
"platforms": SUPPORTED_PLATFORMS,
"usage": "Use action='transpile' with target_platform to convert an agent"
}, indent=2)
def _analyze(self, **kwargs) -> str:
"""Analyze an agent and recommend the best target platform."""
agent_name = kwargs.get("agent_name")
agent_json = kwargs.get("agent_json")
if not agent_name and not agent_json:
return json.dumps({
"status": "error",
"error": "Provide either agent_name or agent_json"
})
# Load agent definition
agent_def = agent_json or self._load_agent_definition(agent_name)
if not agent_def:
return json.dumps({
"status": "error",
"error": f"Could not load agent: {agent_name}"
})
# Analyze complexity
analysis = self._analyze_agent_complexity(agent_def)
return json.dumps({
"status": "success",
"agent_name": agent_def.get("agent", {}).get("name", agent_name),
"analysis": analysis,
"recommendations": self._generate_platform_recommendations(analysis)
}, indent=2)
def _preview(self, **kwargs) -> str:
"""Preview transpilation without saving files."""
kwargs["save_files"] = False
return self._transpile(**kwargs)
def _transpile(self, **kwargs) -> str:
"""Transpile an agent to the target platform."""
agent_name = kwargs.get("agent_name")
agent_json = kwargs.get("agent_json")
target_platform = kwargs.get("target_platform", "m365_copilot")
save_files = kwargs.get("save_files", False)
function_app_url = kwargs.get("function_app_url", "https://your-function-app.azurewebsites.net")
if not agent_name and not agent_json:
return json.dumps({
"status": "error",
"error": "Provide either agent_name or agent_json"
})
# Load agent definition
agent_def = agent_json or self._load_agent_definition(agent_name)
if not agent_def:
return json.dumps({
"status": "error",
"error": f"Could not load agent: {agent_name}"
})
results = {}
platforms_to_generate = (
list(SUPPORTED_PLATFORMS.keys())
if target_platform == "all"
else [target_platform]
)
for platform in platforms_to_generate:
if platform == "m365_copilot":
results[platform] = self._transpile_to_m365(agent_def, function_app_url)
elif platform == "copilot_studio":
results[platform] = self._transpile_to_copilot_studio(agent_def, function_app_url)
elif platform == "azure_foundry":
results[platform] = self._transpile_to_azure_foundry(agent_def, function_app_url)
# Save files if requested
if save_files:
saved_paths = self._save_transpiled_files(agent_name or "agent", results)
# Create a preview by truncating long string values
def truncate_value(v):
if isinstance(v, str) and len(v) > 500:
return v[:500] + "..."
return str(v)[:500] + "..." if len(str(v)) > 500 else v
preview = {}
for platform, files in results.items():
preview[platform] = {fk: truncate_value(fv) for fk, fv in files.items()}
return json.dumps({
"status": "success",
"message": "Files generated and saved",
"saved_paths": saved_paths,
"preview": preview
}, indent=2)
return json.dumps({
"status": "success",
"transpiled": results
}, indent=2)
def _batch_transpile(self, **kwargs) -> str:
"""Transpile multiple agents at once."""
agent_names = kwargs.get("agent_names", [])
target_platform = kwargs.get("target_platform", "all")
if not agent_names:
# Get all agents from demos folder
agent_names = self._list_available_agents()
results = {}
for name in agent_names:
result = json.loads(self._transpile(
agent_name=name,
target_platform=target_platform,
save_files=kwargs.get("save_files", False),
function_app_url=kwargs.get("function_app_url")
))
results[name] = result.get("status")
return json.dumps({
"status": "success",
"processed": len(results),
"results": results
}, indent=2)
def _generate_openapi(self, **kwargs) -> str:
"""Generate OpenAPI spec for the RAPP Function App."""
function_app_url = kwargs.get("function_app_url", "https://your-function-app.azurewebsites.net")
include_agents = kwargs.get("include_agents", None)
# Get all agents or filter
agents = []
if include_agents:
for name in include_agents:
agent_def = self._load_agent_definition(name)
if agent_def:
agents.append(agent_def)
else:
for name in self._list_available_agents():
agent_def = self._load_agent_definition(name)
if agent_def:
agents.append(agent_def)
openapi_spec = self._build_openapi_spec(agents, function_app_url)
return json.dumps({
"status": "success",
"openapi_spec": openapi_spec,
"agents_included": len(agents)
}, indent=2)
# =========================================================================
# PLATFORM-SPECIFIC TRANSPILERS
# =========================================================================
def _transpile_to_m365(self, agent_def: Dict, function_app_url: str) -> Dict:
"""Transpile to M365 Copilot Declarative Agent format."""
agent_info = agent_def.get("agent", agent_def)
agent_name = agent_info.get("name", agent_info.get("agent_name", "RAPPAgent"))
description = agent_info.get("description", "RAPP Agent")
# Build instructions from system_prompt or description
instructions = agent_def.get("system_prompt", agent_def.get("systemPrompt", ""))
if not instructions:
instructions = f"You are {agent_name}. {description}"
# Get actions/capabilities
actions = agent_def.get("actions", [])
metadata = agent_def.get("metadata", {})
# Build conversation starters from demo_conversation
conversation_starters = []
demo_conv = agent_def.get("demo_conversation", agent_def.get("demoConversation", []))
for msg in demo_conv:
if msg.get("role") == "user":
conversation_starters.append({
"title": msg.get("content", "")[:50],
"text": msg.get("content", "")
})
# Limit to 6 starters
conversation_starters = conversation_starters[:6]
# Build declarative agent manifest
declarative_agent = {
"$schema": f"https://developer.microsoft.com/json-schemas/copilot/declarative-agent/{M365_MANIFEST_VERSION}/schema.json",
"version": M365_MANIFEST_VERSION,
"name": agent_name,
"description": description[:1000],
"instructions": instructions[:8000],
"conversation_starters": conversation_starters,
"actions": [
{
"id": f"{self._to_snake_case(agent_name)}_plugin",
"file": f"{self._to_snake_case(agent_name)}-plugin.json"
}
]
}
# Build API plugin manifest
plugin_manifest = self._build_plugin_manifest(agent_def, function_app_url)
# Build OpenAPI spec for this specific agent
openapi_spec = self._build_agent_openapi(agent_def, function_app_url)
return {
"declarativeAgent.json": declarative_agent,
"plugin.json": plugin_manifest,
"openapi.yaml": openapi_spec
}
def _transpile_to_copilot_studio(self, agent_def: Dict, function_app_url: str) -> Dict:
"""Transpile to Copilot Studio format."""
agent_info = agent_def.get("agent", agent_def)
agent_name = agent_info.get("name", agent_info.get("agent_name", "RAPPAgent"))
description = agent_info.get("description", "RAPP Agent")
# Build system topic with instructions
instructions = agent_def.get("system_prompt", agent_def.get("systemPrompt", ""))
# Build topics from actions
topics = {}
actions = agent_def.get("actions", [])
for i, action in enumerate(actions):
action_name = action.get("name", f"action_{i}")
topic_name = self._to_title_case(action_name)
# Get trigger phrases
trigger_phrases = [action_name.replace("_", " ")]
if action.get("description"):
trigger_phrases.append(action["description"][:50])
# Build topic YAML
topics[f"topic_{action_name}.yaml"] = {
"kind": "AdaptiveDialog",
"name": topic_name,
"triggerQueries": trigger_phrases,
"actions": [
{
"kind": "InvokeFlowAction",
"flowId": f"/flows/rapp-{self._to_snake_case(agent_name)}",
"inputs": {
"action": action_name,
"parameters": action.get("parameters", [])
}
},
{
"kind": "SendMessage",
"message": f"I've completed the {topic_name} action. Is there anything else you'd like me to do?"
}
]
}
# Build main agent configuration
agent_config = {
"schemaVersion": "1.0",
"kind": "Bot",
"metadata": {
"name": agent_name,
"description": description,
"icon": agent_info.get("icon", "fa-robot"),
"category": agent_info.get("category", "productivity")
},
"language": {
"primaryLanguage": "en-us"
},
"systemTopic": {
"kind": "SystemTopic",
"name": "System",
"instructions": instructions[:4000] if instructions else description
},
"topics": list(topics.keys()),
"connectors": [
{
"id": f"rapp-{self._to_snake_case(agent_name)}-connector",
"type": "CustomConnector",
"apiDefinitionUrl": f"{function_app_url}/api/openapi"
}
]
}
# Build Power Automate flow template
flow_template = self._build_power_automate_flow(agent_def, function_app_url)
result = {
"agent.yaml": agent_config,
"flow_template.json": flow_template
}
result.update(topics)
return result
def _transpile_to_azure_foundry(self, agent_def: Dict, function_app_url: str) -> Dict:
"""Transpile to Azure AI Foundry Agent format."""
agent_info = agent_def.get("agent", agent_def)
agent_name = agent_info.get("name", agent_info.get("agent_name", "RAPPAgent"))
class_name = self._to_pascal_case(agent_name)
snake_name = self._to_snake_case(agent_name)
description = agent_info.get("description", "RAPP Agent")
# Get actions
actions = agent_def.get("actions", [])
# Build tools.py with function definitions
tools_code = self._generate_foundry_tools(agent_def)
# Build agent.py
agent_code = f'''"""
Azure AI Foundry Agent: {agent_name}
Auto-generated from RAPP agent definition
Description: {description}
"""
import os
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
from azure.ai.projects.models import (
AgentThread,
MessageRole,
FunctionTool,
ToolSet
)
from {snake_name}_tools import get_tools, execute_tool
class {class_name}Agent:
"""
{description}
This agent was transpiled from RAPP format for Azure AI Foundry.
"""
def __init__(self, project_connection_string: str = None):
self.project_connection_string = project_connection_string or os.environ.get("AI_PROJECT_CONNECTION_STRING")
self.credential = DefaultAzureCredential()
self.client = AIProjectClient.from_connection_string(
credential=self.credential,
conn_str=self.project_connection_string
)
self.agent = None
self.thread = None
def create_agent(self):
"""Create the AI agent with tools."""
tools = get_tools()
self.agent = self.client.agents.create_agent(
model="gpt-4o",
name="{agent_name}",
instructions="""{description}
{agent_def.get("system_prompt", agent_def.get("systemPrompt", ""))}""",
tools=tools
)
self.thread = self.client.agents.create_thread()
return self.agent.id
def chat(self, user_message: str) -> str:
"""Send a message and get a response."""
if not self.agent or not self.thread:
self.create_agent()
# Create message
self.client.agents.create_message(
thread_id=self.thread.id,
role=MessageRole.USER,
content=user_message
)
# Run the agent
run = self.client.agents.create_run(
thread_id=self.thread.id,
agent_id=self.agent.id
)
# Poll for completion and handle tool calls
while run.status in ["queued", "in_progress", "requires_action"]:
if run.status == "requires_action":
tool_outputs = []
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
result = execute_tool(
tool_call.function.name,
tool_call.function.arguments
)
tool_outputs.append({{
"tool_call_id": tool_call.id,
"output": result
}})
run = self.client.agents.submit_tool_outputs(
thread_id=self.thread.id,
run_id=run.id,
tool_outputs=tool_outputs
)
else:
import time
time.sleep(1)
run = self.client.agents.get_run(
thread_id=self.thread.id,
run_id=run.id
)
# Get the response
messages = self.client.agents.list_messages(thread_id=self.thread.id)
return messages.data[0].content[0].text.value
def cleanup(self):
"""Clean up resources."""
if self.agent:
self.client.agents.delete_agent(self.agent.id)
if self.thread:
self.client.agents.delete_thread(self.thread.id)
# Usage example
if __name__ == "__main__":
agent = {class_name}Agent()
agent.create_agent()
response = agent.chat("What can you help me with?")
print(response)
agent.cleanup()
'''
# Build config.yaml
config = {
"agent": {
"name": agent_name,
"description": description,
"model": "gpt-4o",
"version": "1.0.0"
},
"rapp_backend": {
"url": function_app_url,
"enabled": True
},
"tools": [a.get("name") for a in actions],
"environment": {
"AI_PROJECT_CONNECTION_STRING": "${AI_PROJECT_CONNECTION_STRING}",
"RAPP_FUNCTION_APP_URL": function_app_url
}
}
return {
f"{snake_name}_agent.py": agent_code,
f"{snake_name}_tools.py": tools_code,
"config.yaml": config,
"requirements.txt": "azure-ai-projects>=1.0.0\nazure-identity>=1.15.0\nrequests>=2.31.0"
}
# =========================================================================
# HELPER METHODS
# =========================================================================
def _load_agent_definition(self, agent_name: str) -> Optional[Dict]:
"""Load agent definition from demos folder."""
# Try different naming patterns
patterns = [
f"{agent_name}.json",
f"{self._to_snake_case(agent_name)}.json",
f"{self._to_snake_case(agent_name)}_agent.json",
]
for pattern in patterns:
path = os.path.join(self.demos_path, pattern)
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
return None
def _list_available_agents(self) -> List[str]:
"""List all available agent definitions."""
agents = []
if os.path.exists(self.demos_path):
for f in os.listdir(self.demos_path):
if f.endswith('.json') and 'agent' in f.lower():
agents.append(f.replace('.json', ''))
return agents
def _analyze_agent_complexity(self, agent_def: Dict) -> Dict:
"""Analyze agent complexity for platform recommendations."""
actions = agent_def.get("actions", [])
has_swarm = "swarm_agents" in agent_def
has_external_api = any("api" in str(a).lower() or "http" in str(a).lower() for a in actions)
return {
"action_count": len(actions),
"has_swarm_orchestration": has_swarm,
"has_external_api_calls": has_external_api,
"complexity_score": len(actions) + (10 if has_swarm else 0) + (5 if has_external_api else 0),
"has_system_prompt": bool(agent_def.get("system_prompt") or agent_def.get("systemPrompt")),
"has_demo_conversation": bool(agent_def.get("demo_conversation") or agent_def.get("demoConversation"))
}
def _generate_platform_recommendations(self, analysis: Dict) -> List[Dict]:
"""Generate platform recommendations based on analysis."""
recs = []
complexity = analysis.get("complexity_score", 0)
# M365 Copilot - good for moderate complexity with M365 integration
recs.append({
"platform": "m365_copilot",
"score": 80 if complexity < 20 else 60,
"reason": "Best for Teams/Outlook integration with moderate complexity",
"pros": ["Native M365 integration", "Declarative approach", "Easy deployment"],
"cons": ["Limited to API plugin actions", "8K instruction limit"]
})
# Copilot Studio - good for low-code scenarios
recs.append({
"platform": "copilot_studio",
"score": 90 if complexity < 10 else 50,
"reason": "Best for low-code scenarios and Power Platform integration",
"pros": ["Visual designer", "Power Automate flows", "Easy for business users"],
"cons": ["Less flexibility", "May need multiple flows for complex logic"]
})
# Azure Foundry - good for complex scenarios
recs.append({
"platform": "azure_foundry",
"score": 90 if complexity >= 15 else 70,
"reason": "Best for complex orchestration and custom logic",
"pros": ["Full Python control", "Complex tool chains", "Swarm support"],
"cons": ["Requires coding", "More setup"]
})
# Sort by score
recs.sort(key=lambda x: x["score"], reverse=True)
return recs
def _build_plugin_manifest(self, agent_def: Dict, function_app_url: str) -> Dict:
"""Build API plugin manifest for M365 Copilot."""
agent_info = agent_def.get("agent", agent_def)
agent_name = agent_info.get("name", agent_info.get("agent_name", "RAPPAgent"))
return {
"$schema": "https://developer.microsoft.com/json-schemas/copilot/plugin/v2.2/schema.json",
"schema_version": "v2.2",
"name_for_human": agent_name,
"description_for_human": agent_info.get("description", "")[:100],
"description_for_model": agent_info.get("description", "")[:500],
"api": {
"type": "openapi",
"url": f"{function_app_url}/api/openapi/{self._to_snake_case(agent_name)}"
},
"auth": {
"type": "none"
},
"capabilities": {
"conversation_starters": True
}
}
def _build_agent_openapi(self, agent_def: Dict, function_app_url: str) -> str:
"""Build OpenAPI spec for a single agent."""
agent_info = agent_def.get("agent", agent_def)
agent_name = agent_info.get("name", agent_info.get("agent_name", "RAPPAgent"))
snake_name = self._to_snake_case(agent_name)
actions = agent_def.get("actions", [])
metadata = agent_def.get("metadata", {})
paths = {}
# Main agent endpoint
paths[f"/api/{snake_name}"] = {
"post": {
"operationId": f"{snake_name}_invoke",
"summary": f"Invoke {agent_name}",
"description": agent_info.get("description", ""),
"requestBody": {
"required": True,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"action": {
"type": "string",
"description": "The action to perform",
"enum": [a.get("name") for a in actions] if actions else ["default"]
},
"parameters": {
"type": "object",
"description": "Action-specific parameters"
}
},
"required": ["action"]
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
}
spec = {
"openapi": "3.0.3",
"info": {
"title": f"{agent_name} API",
"description": agent_info.get("description", ""),
"version": agent_info.get("version", "1.0.0")
},
"servers": [
{"url": function_app_url}
],
"paths": paths
}
# Return as YAML-like string (simplified)
return json.dumps(spec, indent=2)
def _build_openapi_spec(self, agents: List[Dict], function_app_url: str) -> Dict:
"""Build complete OpenAPI spec for all agents."""
paths = {}
for agent_def in agents:
agent_info = agent_def.get("agent", agent_def)
agent_name = agent_info.get("name", agent_info.get("agent_name", "Agent"))
snake_name = self._to_snake_case(agent_name)
paths[f"/api/{snake_name}"] = {
"post": {
"operationId": f"{snake_name}_invoke",
"summary": f"Invoke {agent_name}",
"description": agent_info.get("description", ""),
"requestBody": {
"required": True,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"action": {"type": "string"},
"parameters": {"type": "object"}
}
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {"schema": {"type": "object"}}
}
}
}
}
}
return {
"openapi": "3.0.3",
"info": {
"title": "RAPP Agent API",
"description": "Multi-agent platform API",
"version": "1.0.0"
},
"servers": [{"url": function_app_url}],
"paths": paths
}
def _build_power_automate_flow(self, agent_def: Dict, function_app_url: str) -> Dict:
"""Build Power Automate flow template for Copilot Studio."""
agent_info = agent_def.get("agent", agent_def)
agent_name = agent_info.get("name", agent_info.get("agent_name", "RAPPAgent"))
return {
"name": f"RAPP-{agent_name}-Flow",
"description": f"Power Automate flow for {agent_name}",
"trigger": {
"type": "Request",
"kind": "Http",
"inputs": {
"schema": {
"type": "object",
"properties": {
"action": {"type": "string"},
"parameters": {"type": "object"}
}
}
}
},
"actions": {
"Call_RAPP_Function": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": f"{function_app_url}/api/{self._to_snake_case(agent_name)}",
"headers": {
"Content-Type": "application/json"
},
"body": "@triggerBody()"
}
},
"Response": {
"type": "Response",
"inputs": {
"statusCode": 200,
"body": "@body('Call_RAPP_Function')"
},
"runAfter": {"Call_RAPP_Function": ["Succeeded"]}
}
}
}
def _generate_foundry_tools(self, agent_def: Dict) -> str:
"""Generate tools.py for Azure AI Foundry."""
agent_info = agent_def.get("agent", agent_def)
agent_name = agent_info.get("name", agent_info.get("agent_name", "RAPPAgent"))
snake_name = self._to_snake_case(agent_name)
actions = agent_def.get("actions", [])
tools_code = f'''"""
Tools for {agent_name} Azure AI Foundry Agent
Auto-generated from RAPP agent definition
"""
import json
import requests
from typing import Dict, Any, List
from azure.ai.projects.models import FunctionTool
RAPP_FUNCTION_APP_URL = "https://your-function-app.azurewebsites.net"
def get_tools() -> List[FunctionTool]:
"""Get all tools for this agent."""
tools = []
'''
# Add tool definitions for each action
for action in actions:
action_name = action.get("name", "unknown")
description = action.get("description", f"Execute {action_name}")
params = action.get("parameters", [])
# Build parameters schema
param_props = {}
for p in params:
if isinstance(p, str):
param_props[p] = {"type": "string", "description": f"The {p} parameter"}
elif isinstance(p, dict):
param_props[p.get("name", "param")] = {
"type": p.get("type", "string"),
"description": p.get("description", "")
}
tools_code += f''' tools.append(FunctionTool(
name="{action_name}",
description="{description}",
parameters={{
"type": "object",
"properties": {json.dumps(param_props, indent=12)},
"required": []
}}
))
'''
tools_code += ''' return tools
def execute_tool(tool_name: str, arguments: str) -> str:
"""Execute a tool by calling the RAPP Function App."""
try:
args = json.loads(arguments) if arguments else {}
response = requests.post(
f"{RAPP_FUNCTION_APP_URL}/api/''' + snake_name + '''",
json={
"action": tool_name,
**args
},
timeout=60
)
if response.status_code == 200:
return json.dumps(response.json())
else:
return json.dumps({"error": f"API returned {response.status_code}"})
except Exception as e:
return json.dumps({"error": str(e)})
'''
return tools_code
def _save_transpiled_files(self, agent_name: str, results: Dict) -> Dict:
"""Save transpiled files to disk."""
saved = {}
base_output = os.path.join(self.output_path, self._to_snake_case(agent_name))
for platform, files in results.items():
platform_path = os.path.join(base_output, platform)
os.makedirs(platform_path, exist_ok=True)
saved[platform] = []
for filename, content in files.items():
filepath = os.path.join(platform_path, filename)
# Create subdirectories if needed
os.makedirs(os.path.dirname(filepath), exist_ok=True) if os.path.dirname(filepath) != platform_path else None
with open(filepath, 'w') as f:
if isinstance(content, (dict, list)):
json.dump(content, f, indent=2)
else:
f.write(str(content))
saved[platform].append(filepath)
return saved
# String utilities
def _to_snake_case(self, name: str) -> str:
"""Convert to snake_case."""
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower().replace(' ', '_').replace('-', '_')
def _to_pascal_case(self, name: str) -> str:
"""Convert to PascalCase."""
return ''.join(word.capitalize() for word in re.split(r'[_\s-]', name))
def _to_title_case(self, name: str) -> str:
"""Convert to Title Case."""
return ' '.join(word.capitalize() for word in re.split(r'[_\s-]', name))