Skip to content
Open
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
48 changes: 11 additions & 37 deletions src/google/adk/cli/adk_web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,30 +601,11 @@ def _import_plugin_object(self, qualified_name: str) -> Any:
module = importlib.import_module(module_name)
return getattr(module, obj_name)

def _setup_runtime_config(self, web_assets_dir: str):
"""Sets up the runtime config for the web server."""
# Read existing runtime config file.
runtime_config_path = os.path.join(
web_assets_dir, "assets", "config", "runtime-config.json"
)
runtime_config = {}
try:
with open(runtime_config_path, "r") as f:
runtime_config = json.load(f)
except FileNotFoundError:
logger.info(
"File not found: %s. A new runtime config file will be created.",
runtime_config_path,
)
except json.JSONDecodeError:
logger.warning(
"Failed to decode JSON from %s. The file content will be"
" overwritten.",
runtime_config_path,
)
runtime_config["backendUrl"] = self.url_prefix if self.url_prefix else ""

# Set custom logo config.
def _build_runtime_config(self) -> dict[str, Any]:
"""Builds the runtime config dict for the dev UI frontend."""
runtime_config: dict[str, Any] = {
"backendUrl": self.url_prefix if self.url_prefix else "",
}
if self.logo_text or self.logo_image_url:
if not self.logo_text or not self.logo_image_url:
raise ValueError(
Expand All @@ -635,18 +616,7 @@ def _setup_runtime_config(self, web_assets_dir: str):
"text": self.logo_text,
"imageUrl": self.logo_image_url,
}
elif "logo" in runtime_config:
del runtime_config["logo"]

# Write the runtime config file.
try:
os.makedirs(os.path.dirname(runtime_config_path), exist_ok=True)
with open(runtime_config_path, "w") as f:
json.dump(runtime_config, f, indent=2)
except IOError as e:
logger.error(
"Failed to write runtime config file %s: %s", runtime_config_path, e
)
return runtime_config

async def _create_session(
self,
Expand Down Expand Up @@ -742,7 +712,7 @@ async def internal_lifespan(app: FastAPI):
],
)
if web_assets_dir:
self._setup_runtime_config(web_assets_dir)
self._build_runtime_config()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The call to _build_runtime_config() here is used for validation at startup, but its return value is ignored. While this correctly triggers a ValueError if the logo configuration is invalid, it might be confusing to other developers. Consider adding a brief comment to clarify that this call is for validation purposes.


# TODO - register_processors to be removed once --otel_to_cloud is no
# longer experimental.
Expand Down Expand Up @@ -1816,6 +1786,10 @@ async def get_ui_config():
"logo_image_url": self.logo_image_url,
}

@app.get("/dev-ui/assets/config/runtime-config.json")
async def get_runtime_config():
return self._build_runtime_config()

@app.get("/")
async def redirect_root_to_dev_ui():
return RedirectResponse(redirect_dev_ui_url)
Expand Down
67 changes: 67 additions & 0 deletions tests/unittests/cli/test_adk_web_server_run_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,70 @@ async def _get_runner_async(_self, _app_name: str):
run_config.session_resumption.transparent
is expected_session_resumption_transparent
)


def _make_server(**kwargs) -> AdkWebServer:
defaults = dict(
agent_loader=_DummyAgentLoader(),
session_service=InMemorySessionService(),
memory_service=types.SimpleNamespace(),
artifact_service=types.SimpleNamespace(),
credential_service=types.SimpleNamespace(),
eval_sets_manager=types.SimpleNamespace(),
eval_set_results_manager=types.SimpleNamespace(),
agents_dir=".",
)
defaults.update(kwargs)
return AdkWebServer(**defaults)


class TestBuildRuntimeConfig:

def test_default_no_prefix(self):
server = _make_server()
config = server._build_runtime_config()
assert config == {"backendUrl": ""}

def test_with_url_prefix(self):
server = _make_server(url_prefix="/my-prefix")
config = server._build_runtime_config()
assert config == {"backendUrl": "/my-prefix"}

def test_with_logo(self):
server = _make_server(
logo_text="My App",
logo_image_url="https://example.com/logo.png",
)
config = server._build_runtime_config()
assert config == {
"backendUrl": "",
"logo": {
"text": "My App",
"imageUrl": "https://example.com/logo.png",
},
}

def test_logo_text_only_raises(self):
server = _make_server(logo_text="My App")
with pytest.raises(ValueError, match="Both --logo-text and --logo-image-url"):
server._build_runtime_config()

def test_logo_image_url_only_raises(self):
server = _make_server(logo_image_url="https://example.com/logo.png")
with pytest.raises(ValueError, match="Both --logo-text and --logo-image-url"):
server._build_runtime_config()


class TestRuntimeConfigEndpoint:

def test_get_runtime_config_json(self, tmp_path):
server = _make_server(url_prefix="/test-prefix")
app = server.get_fast_api_app(
setup_observer=lambda _observer, _server: None,
tear_down_observer=lambda _observer, _server: None,
web_assets_dir=str(tmp_path),
)
client = TestClient(app)
resp = client.get("/dev-ui/assets/config/runtime-config.json")
assert resp.status_code == 200
assert resp.json() == {"backendUrl": "/test-prefix"}