From dac96926472e67eb39d309cf1cd2cb1761ed30d3 Mon Sep 17 00:00:00 2001 From: evanlowe <62918515+evanlowe@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:04:30 +0800 Subject: [PATCH] fix(harness): enable session capability overlays --- tests/cloud/test_harness_app_http.py | 95 ++++++++++++++++++++++++++++ veadk/cloud/harness_app/app.py | 13 ++++ veadk/integrations/agentkit/app.py | 6 +- 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 tests/cloud/test_harness_app_http.py diff --git a/tests/cloud/test_harness_app_http.py b/tests/cloud/test_harness_app_http.py new file mode 100644 index 00000000..b4fb808a --- /dev/null +++ b/tests/cloud/test_harness_app_http.py @@ -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 + ) diff --git a/veadk/cloud/harness_app/app.py b/veadk/cloud/harness_app/app.py index e42ca569..e0ede980 100644 --- a/veadk/cloud/harness_app/app.py +++ b/veadk/cloud/harness_app/app.py @@ -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 @@ -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) diff --git a/veadk/integrations/agentkit/app.py b/veadk/integrations/agentkit/app.py index 026d1743..27fac630 100644 --- a/veadk/integrations/agentkit/app.py +++ b/veadk/integrations/agentkit/app.py @@ -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) @@ -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"}: