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
95 changes: 95 additions & 0 deletions tests/cloud/test_harness_app_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# 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.

import importlib

from fastapi.testclient import TestClient


def test_harness_app_exposes_agent_info(monkeypatch):
monkeypatch.setenv("MODEL_AGENT_API_KEY", "test-api-key")
monkeypatch.setenv("MODEL_NAME", "test-model")
monkeypatch.setenv("HARNESS_NAME", "test-harness")

harness_module = importlib.import_module("veadk.cloud.harness_app.app")

with TestClient(harness_module.app) as client:
app_name = client.get("/list-apps").json()[0]
response = client.get(f"/web/agent-info/{app_name}")

assert response.status_code == 200
info = response.json()
assert info["name"] == "test_harness"
assert info["model"] == "openai/test-model"
assert info["tools"] == []
assert info["skills"] == []
assert info["subAgents"] == []
assert info["graph"]["id"] == "test_harness"
assert info["graph"]["path"] == ["test_harness"]
assert info["graph"]["children"] == []
assert client.get("/web/agent-info/unknown").status_code == 404


def test_harness_app_supports_session_capability_overrides(monkeypatch):
monkeypatch.setenv("MODEL_AGENT_API_KEY", "test-api-key")
monkeypatch.setenv("MODEL_NAME", "test-model")
monkeypatch.setenv("HARNESS_NAME", "test-harness")

harness_module = importlib.import_module("veadk.cloud.harness_app.app")

with TestClient(harness_module.app) as client:
app_name = client.get("/list-apps").json()[0]
created = client.post(
f"/apps/{app_name}/users/test-user/sessions",
json={},
)
assert created.status_code == 200
session_id = created.json()["id"]
capabilities_path = (
f"/harness/apps/{app_name}/users/test-user/sessions/"
f"{session_id}/capabilities"
)

initial = client.get(capabilities_path)
assert initial.status_code == 200
assert initial.json()["revision"] == 0

added = client.post(
capabilities_path,
json={
"kind": "tool",
"name": "get_city_weather",
"expected_revision": 0,
},
)
assert added.status_code == 200
assert added.json()["revision"] == 1
assert any(
item["id"] == "session:tool:get_city_weather" and item["custom"] is True
for item in added.json()["tools"]
)

removed = client.delete(
capabilities_path + "/session:tool:get_city_weather",
params={"expected_revision": 1},
)
assert removed.status_code == 200
assert removed.json()["revision"] == 2
assert not any(item["custom"] for item in removed.json()["tools"])

assert client.get("/harness/capabilities/tools").status_code == 200
assert any(
getattr(route, "path", None) == "/harness/run_sse"
for route in harness_module.app.router.routes
)
13 changes: 13 additions & 0 deletions veadk/cloud/harness_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@
has_a2a_registry_config,
spawn_harness_run_agent,
)
from veadk.integrations.agentkit.app import (
_ADK_SERVER_STATE_KEY,
_add_introspection_routes,
_configure_session_capability_routes,
)
from veadk.memory.short_term_memory import ShortTermMemory
from veadk.runner import Runner
from veadk.utils.logger import get_logger
Expand Down Expand Up @@ -211,6 +216,14 @@ async def lifespan(app: FastAPI):
# Base app = ADK api routes; then add /harness/invoke; mount A2A last so
# it catches the well-known / RPC paths the ADK routes don't claim.
self.app = self._server.get_fast_api_app(lifespan=lifespan)
setattr(self.app.state, _ADK_SERVER_STATE_KEY, self._server)
_configure_session_capability_routes(self.app, self.agent)
_add_introspection_routes(
self.app,
self.agent,
{},
app_name=self.harness_name,
)
self.mount()
self._mount_run_sse_override()
self.app.mount("/", self._a2a_app)
Expand Down
6 changes: 4 additions & 2 deletions veadk/integrations/agentkit/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,14 +378,17 @@ def _add_introspection_routes(
app: FastAPI,
root_agent: BaseAgent,
display_names: Mapping[str, str],
*,
app_name: str | None = None,
) -> None:
expected_name = app_name or str(getattr(root_agent, "name", "") or "")

@app.get("/ping")
def ping() -> dict[str, str]:
return {"status": "ok"}

@app.get("/web/agent-info/{app_name}")
def agent_info(app_name: str) -> dict[str, Any]:
expected_name = str(getattr(root_agent, "name", "") or "")
if app_name != expected_name:
raise HTTPException(status_code=404, detail="unknown agent: " + app_name)
node = _agent_node(root_agent, display_names)
Expand Down Expand Up @@ -413,7 +416,6 @@ async def agent_search(
q: str,
user_id: str = "",
) -> dict[str, Any]:
expected_name = str(getattr(root_agent, "name", "") or "")
if app_name != expected_name:
raise HTTPException(status_code=404, detail="unknown agent: " + app_name)
if source not in {"knowledge", "memory"}:
Expand Down
Loading