Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 125 additions & 26 deletions src/sk-agents/manual_test/validate_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,20 @@


def print_header(title: str):
"""Print a formatted section header."""
print(f"\n{'='*60}")
print(f" {title}")
print(f"{'='*60}")


def print_response(data: dict):
print(f" Response JSON:")
"""Print the JSON response data."""
print(" Response JSON:")
print(f" {json.dumps(data, indent=4)}")


def check(label: str, condition: bool, actual=None, expected=None):
"""Check a condition and print PASS/FAIL result."""
if condition:
print(f" {PASS} {label}")
else:
Expand Down Expand Up @@ -98,11 +101,46 @@ def test_appv1_metadata():
print_response(data)

results = []
results.append(check("Status code is 200", response.status_code == 200, response.status_code, 200))
results.append(check("agent_name is 'WeatherBot'", data["agent_name"] == "WeatherBot", data["agent_name"], "WeatherBot"))
results.append(check("description is 'A weather chat agent'", data["description"] == "A weather chat agent", data["description"], "A weather chat agent"))
results.append(check("model is 'gpt-4o'", data["model"] == "gpt-4o", data["model"], "gpt-4o"))
results.append(check("plugins contains 'WeatherPlugin'", data["plugins"] == ["WeatherPlugin"], data["plugins"], ["WeatherPlugin"]))
results.append(
check(
"Status code is 200",
response.status_code == 200,
response.status_code,
200,
)
)
results.append(
check(
"agent_name is 'WeatherBot'",
data["agent_name"] == "WeatherBot",
data["agent_name"],
"WeatherBot",
)
)
results.append(
check(
"description is 'A weather chat agent'",
data["description"] == "A weather chat agent",
data["description"],
"A weather chat agent",
)
)
results.append(
check(
"model is 'gpt-4o'",
data["model"] == "gpt-4o",
data["model"],
"gpt-4o",
)
)
results.append(
check(
"plugins contains 'WeatherPlugin'",
data["plugins"] == ["WeatherPlugin"],
data["plugins"],
["WeatherPlugin"],
)
)
return all(results)


Expand Down Expand Up @@ -143,11 +181,46 @@ def test_appv3_metadata():
print_response(data)

results = []
results.append(check("Status code is 200", response.status_code == 200, response.status_code, 200))
results.append(check("agent_name is 'MathAgent'", data["agent_name"] == "MathAgent", data["agent_name"], "MathAgent"))
results.append(check("description is 'A math helper agent'", data["description"] == "A math helper agent", data["description"], "A math helper agent"))
results.append(check("model is 'gpt-4o-2024-05-13'", data["model"] == "gpt-4o-2024-05-13", data["model"], "gpt-4o-2024-05-13"))
results.append(check("plugins contains 'sensitive_plugin'", data["plugins"] == ["sensitive_plugin"], data["plugins"], ["sensitive_plugin"]))
results.append(
check(
"Status code is 200",
response.status_code == 200,
response.status_code,
200,
)
)
results.append(
check(
"agent_name is 'MathAgent'",
data["agent_name"] == "MathAgent",
data["agent_name"],
"MathAgent",
)
)
results.append(
check(
"description is 'A math helper agent'",
data["description"] == "A math helper agent",
data["description"],
"A math helper agent",
)
)
results.append(
check(
"model is 'gpt-4o-2024-05-13'",
data["model"] == "gpt-4o-2024-05-13",
data["model"],
"gpt-4o-2024-05-13",
)
)
results.append(
check(
"plugins contains 'sensitive_plugin'",
data["plugins"] == ["sensitive_plugin"],
data["plugins"],
["sensitive_plugin"],
)
)
return all(results)


Expand Down Expand Up @@ -185,9 +258,26 @@ def test_appv1_no_plugins():

results = []
results.append(check("Status code is 200", response.status_code == 200))
results.append(check("agent_name is 'ChatBot'", data["agent_name"] == "ChatBot"))
results.append(check("model is 'gpt-4o-mini'", data["model"] == "gpt-4o-mini"))
results.append(check("plugins is None (no plugins)", data["plugins"] is None, data["plugins"], None))
results.append(
check(
"agent_name is 'ChatBot'",
data["agent_name"] == "ChatBot",
)
)
results.append(
check(
"model is 'gpt-4o-mini'",
data["model"] == "gpt-4o-mini",
)
)
results.append(
check(
"plugins is None (no plugins)",
data["plugins"] is None,
data["plugins"],
None,
)
)
return all(results)


Expand Down Expand Up @@ -236,12 +326,14 @@ def test_appv3_with_metadata_description():

results = []
results.append(check("Status code is 200", response.status_code == 200))
results.append(check(
"description uses metadata.description (not top-level)",
data["description"] == "A demonstration agent that showcases MCP integration",
data["description"],
"A demonstration agent that showcases MCP integration",
))
results.append(
check(
"description uses metadata.description (not top-level)",
data["description"] == "A demonstration agent that showcases MCP integration",
data["description"],
"A demonstration agent that showcases MCP integration",
)
)
return all(results)


Expand Down Expand Up @@ -270,7 +362,14 @@ def test_response_schema():
print(f" Actual fields: {actual_fields}")

results = []
results.append(check("Response has exactly 4 expected fields", actual_fields == expected_fields, actual_fields, expected_fields))
results.append(
check(
"Response has exactly 4 expected fields",
actual_fields == expected_fields,
actual_fields,
expected_fields,
)
)
return all(results)


Expand All @@ -291,11 +390,11 @@ def test_response_schema():
print_header("FINAL RESULT")
if all_passed:
print(f" {PASS} ALL TESTS PASSED — Ticket is resolved!")
print(f"\n The /metadata endpoint correctly returns:")
print(f" • agent_name — from config name/service_name")
print(f" • description — from metadata.description or top-level")
print(f" • model — from spec.agent.model")
print(f" • plugins — from spec.agent.plugins + remote_plugins + mcp_servers")
print("\n The /metadata endpoint correctly returns:")
print(" • agent_name — from config name/service_name")
print(" • description — from metadata.description or top-level")
print(" • model — from spec.agent.model")
print(" • plugins — from spec.agent.plugins + remote_plugins + mcp_servers")
sys.exit(0)
else:
print(f" {FAIL} SOME TESTS FAILED — See above for details")
Expand Down
8 changes: 8 additions & 0 deletions src/sk-agents/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ dev = [
"mkdocs-material>=9.6.0",
"mkdocstrings[python]>=0.28.0",
"mkdocs-static-i18n>=1.3.0",
"black>=26.3.1",
"pylint>=4.0.5",
]

[tool.ruff]
Expand All @@ -91,6 +93,12 @@ isort = { combine-as-imports = true }
[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.pylint."messages_control"]
disable = ["duplicate-code"]

[tool.pylint.format]
max-line-length = 100

[tool.mypy]
strict = false
disallow_incomplete_defs = false
Expand Down
2 changes: 1 addition & 1 deletion src/sk-agents/src/sk_agents/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class AppVersion(Enum):
raise

try:
(root_handler, api_version) = config.apiVersion.split("/")
root_handler, api_version = config.apiVersion.split("/")
except ValueError:
logger.exception("Invalid API version format")
raise
Expand Down
7 changes: 6 additions & 1 deletion src/sk-agents/src/sk_agents/appv1.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""AppV1 application runner for skagents/v1 API version."""

import os
from datetime import datetime
from typing import Any
Expand All @@ -18,9 +20,12 @@
from sk_agents.utils import initialize_plugin_loader


class AppV1:
class AppV1: # pylint: disable=too-few-public-methods
"""Application runner for skagents/v1 API version."""

@staticmethod
def run(name: str, version: str, app_config: AppConfig, config: BaseConfig, app: FastAPI):
"""Initialize and run the AppV1 application with routes and plugins."""
config_file = app_config.get(TA_SERVICE_CONFIG.env_name)
agents_path = str(os.path.dirname(config_file))

Expand Down
10 changes: 8 additions & 2 deletions src/sk-agents/src/sk_agents/appv3.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ def run(name, version, app_config, config, app):
from sk_agents.utils import initialize_plugin_loader


class AppV3:
class AppV3: # pylint: disable=too-few-public-methods
"""Application runner for tealagents/v1alpha1 API version."""

class StateStores(Enum):
"""Supported state store backends."""

IN_MEMORY = "in-memory"
REDIS = "redis"

Expand Down Expand Up @@ -72,13 +76,14 @@ def _get_auth_storage_manager(app_config: AppConfig):

@staticmethod
def _get_mcp_discovery_manager(app_config: AppConfig):
# pylint: disable=import-outside-toplevel
from sk_agents.mcp_discovery import DiscoveryManagerFactory

discovery_factory = DiscoveryManagerFactory(app_config)
return discovery_factory.get_discovery_manager()

@staticmethod
def _get_auth_manager(app_config: AppConfig):
def _get_auth_manager(app_config: AppConfig): # pylint: disable=unused-argument
# For initial implementation, use mock authentication
# Will be extended in future for Entra ID
return MockAuthenticationManager()
Expand All @@ -103,6 +108,7 @@ def _create_kernel_builder(app_config: AppConfig, authorization: str):

@staticmethod
def run(name: str, version: str, app_config: AppConfig, config: BaseConfig, app: FastAPI):
"""Initialize and run the AppV3 application with routes and plugins."""
if config.apiVersion != "tealagents/v1alpha1":
raise ValueError(
f"AppV3 only supports 'tealagents/v1alpha1' API version, got: {config.apiVersion}"
Expand Down
6 changes: 3 additions & 3 deletions src/sk-agents/src/sk_agents/auth/oauth_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,9 +610,9 @@ async def handle_callback(
code=code,
redirect_uri=server_config.oauth_redirect_uri,
code_verifier=flow_state.verifier,
resource=flow_state.resource
if include_resource
else None, # Conditional per protocol version
resource=(
flow_state.resource if include_resource else None
), # Conditional per protocol version
client_id=server_config.oauth_client_id or client_name,
client_secret=server_config.oauth_client_secret,
requested_scopes=flow_state.scopes, # For scope validation
Expand Down
28 changes: 16 additions & 12 deletions src/sk-agents/src/sk_agents/mcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,9 @@ def apply_trust_level_governance(
logger.debug("Applying sandboxed server governance: elevated restrictions")
return Governance(
requires_hitl=True, # Force HITL for sandboxed servers
cost=base_governance.cost
if base_governance.cost != "low"
else "medium", # Elevate cost
cost=(
base_governance.cost if base_governance.cost != "low" else "medium"
), # Elevate cost
data_sensitivity=base_governance.data_sensitivity,
)
else: # trusted
Expand Down Expand Up @@ -521,13 +521,17 @@ def apply_governance_overrides(

# Apply selective overrides - only override specified fields
return Governance(
requires_hitl=override.requires_hitl
if override.requires_hitl is not None
else base_governance.requires_hitl,
requires_hitl=(
override.requires_hitl
if override.requires_hitl is not None
else base_governance.requires_hitl
),
cost=override.cost if override.cost is not None else base_governance.cost,
data_sensitivity=override.data_sensitivity
if override.data_sensitivity is not None
else base_governance.data_sensitivity,
data_sensitivity=(
override.data_sensitivity
if override.data_sensitivity is not None
else base_governance.data_sensitivity
),
)


Expand Down Expand Up @@ -719,9 +723,9 @@ async def resolve_server_auth_headers(
refresh_request = RefreshTokenRequest(
token_endpoint=token_endpoint,
refresh_token=auth_data.refresh_token,
resource=resource_uri
if include_resource
else None, # Conditional per protocol version
resource=(
resource_uri if include_resource else None
), # Conditional per protocol version
client_id=server_config.oauth_client_id
or app_config.get("TA_OAUTH_CLIENT_NAME"),
client_secret=server_config.oauth_client_secret,
Expand Down
16 changes: 10 additions & 6 deletions src/sk-agents/src/sk_agents/mcp_plugin_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,17 @@ def _apply_governance_overrides(
override = overrides[tool_name]

return Governance(
requires_hitl=override.requires_hitl
if override.requires_hitl is not None
else base_governance.requires_hitl,
requires_hitl=(
override.requires_hitl
if override.requires_hitl is not None
else base_governance.requires_hitl
),
cost=override.cost if override.cost is not None else base_governance.cost,
data_sensitivity=override.data_sensitivity
if override.data_sensitivity is not None
else base_governance.data_sensitivity,
data_sensitivity=(
override.data_sensitivity
if override.data_sensitivity is not None
else base_governance.data_sensitivity
),
)

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
class InMemoryPersistenceManager(TaskPersistenceManager):
def __init__(self):
self.in_memory: dict[str, AgentTask] = {}
self.item_request_id_index: dict[
str, set[str]
] = {} # Maps request_id to set of task_ids that contain it
self.item_request_id_index: dict[str, set[str]] = (
{}
) # Maps request_id to set of task_ids that contain it
logger.info("InMemoryPersistenceManager initialized.")
self._lock = asyncio.Lock()

Expand Down
Loading
Loading