Skip to content

Commit 6e331f5

Browse files
authored
Merge branch 'main' into fix/retry-empty-model-response
2 parents 1d9924b + 2f90c1a commit 6e331f5

25 files changed

Lines changed: 167 additions & 144 deletions

src/google/adk/cli/fast_api.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ def _normalize_relative_path(path: str) -> str:
291291
def _has_parent_reference(path: str) -> bool:
292292
return any(part == ".." for part in path.split("/"))
293293

294-
_ALLOWED_UPLOAD_EXTENSIONS = frozenset({".yaml", ".yml"})
294+
_ALLOWED_EXTENSIONS = frozenset({".yaml", ".yml"})
295295

296296
def _parse_upload_filename(filename: Optional[str]) -> tuple[str, str]:
297297
if not filename:
@@ -307,10 +307,10 @@ def _parse_upload_filename(filename: Optional[str]) -> tuple[str, str]:
307307
if _has_parent_reference(rel_path):
308308
raise ValueError(f"Path traversal rejected: {filename!r}")
309309
ext = os.path.splitext(rel_path)[1].lower()
310-
if ext not in _ALLOWED_UPLOAD_EXTENSIONS:
310+
if ext not in _ALLOWED_EXTENSIONS:
311311
raise ValueError(
312312
f"File type not allowed: {rel_path!r}"
313-
f" (allowed: {', '.join(sorted(_ALLOWED_UPLOAD_EXTENSIONS))})"
313+
f" (allowed: {', '.join(sorted(_ALLOWED_EXTENSIONS))})"
314314
)
315315
return app_name, rel_path
316316

@@ -322,6 +322,12 @@ def _parse_file_path(file_path: str) -> str:
322322
raise ValueError(f"Absolute file_path rejected: {file_path!r}")
323323
if _has_parent_reference(file_path):
324324
raise ValueError(f"Path traversal rejected: {file_path!r}")
325+
ext = os.path.splitext(file_path)[1].lower()
326+
if ext not in _ALLOWED_EXTENSIONS:
327+
raise ValueError(
328+
f"File type not allowed: {file_path!r}"
329+
f" (allowed: {', '.join(sorted(_ALLOWED_EXTENSIONS))})"
330+
)
325331
return file_path
326332

327333
def _resolve_under_dir(root_dir: Path, rel_path: str) -> Path:

src/google/adk/cli/utils/agent_loader.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import logging
2020
import os
2121
from pathlib import Path
22+
import re
2223
import sys
2324
from typing import Any
2425
from typing import Literal
@@ -187,8 +188,36 @@ def _load_from_yaml_config(
187188
) + e.args[1:]
188189
raise e
189190

191+
_VALID_AGENT_NAME_RE = re.compile(r"^[a-zA-Z0-9_]+$")
192+
193+
def _validate_agent_name(self, agent_name: str) -> None:
194+
"""Validate agent name to prevent arbitrary module imports."""
195+
# Strip the special agent prefix for validation
196+
if agent_name.startswith("__"):
197+
name_to_check = agent_name[2:]
198+
check_dir = os.path.abspath(SPECIAL_AGENTS_DIR)
199+
else:
200+
name_to_check = agent_name
201+
check_dir = self.agents_dir
202+
203+
if not self._VALID_AGENT_NAME_RE.match(name_to_check):
204+
raise ValueError(
205+
f"Invalid agent name: {agent_name!r}. Agent names must be valid"
206+
" Python identifiers (letters, digits, and underscores only)."
207+
)
208+
209+
# Verify the agent exists on disk before allowing import
210+
agent_path = Path(check_dir) / name_to_check
211+
agent_file = Path(check_dir) / f"{name_to_check}.py"
212+
if not (agent_path.is_dir() or agent_file.is_file()):
213+
raise ValueError(
214+
f"Agent not found: {agent_name!r}. No matching directory or module"
215+
f" exists in '{os.path.join(check_dir, name_to_check)}'."
216+
)
217+
190218
def _perform_load(self, agent_name: str) -> Union[BaseAgent, App]:
191219
"""Internal logic to load an agent"""
220+
self._validate_agent_name(agent_name)
192221
# Determine the directory to use for loading
193222
if agent_name.startswith("__"):
194223
# Special agent: use special agents directory

src/google/adk/integrations/bigquery/__init__.py

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/google/adk/skills/_utils.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import pathlib
2121
from typing import Union
2222

23+
from google.auth import credentials as auth
2324
from google.cloud import storage
2425
from pydantic import ValidationError
2526
import yaml
@@ -301,6 +302,8 @@ def _list_skills_in_dir(
301302
def _list_skills_in_gcs_dir(
302303
bucket_name: str,
303304
skills_base_path: str = "",
305+
project_id: str | None = None,
306+
credentials: auth.Credentials | None = None,
304307
) -> Dict[str, models.Frontmatter]:
305308
"""List skills in a GCS directory.
306309
@@ -311,7 +314,7 @@ def _list_skills_in_gcs_dir(
311314
Returns:
312315
Dictionary mapping skill IDs to their frontmatter.
313316
"""
314-
client = storage.Client()
317+
client = storage.Client(project=project_id, credentials=credentials)
315318
bucket = client.bucket(bucket_name)
316319

317320
base_prefix = skills_base_path.strip("/")
@@ -350,13 +353,17 @@ def _load_skill_from_gcs_dir(
350353
bucket_name: str,
351354
skill_id: str,
352355
skills_base_path: str = "",
356+
project_id: str | None = None,
357+
credentials: auth.Credentials | None = None,
353358
) -> models.Skill:
354359
"""Load a complete skill from a GCS directory.
355360
356361
Args:
357362
bucket_name: Name of the GCS bucket.
358363
skill_id: The ID of the skill (directory name).
359364
skills_base_path: Base directory within the bucket (e.g., 'path/to/skills').
365+
project_id: Project ID to use for GCS client.
366+
credentials: Credentials to use for GCS client.
360367
361368
Returns:
362369
Skill object with all components loaded.
@@ -366,7 +373,8 @@ def _load_skill_from_gcs_dir(
366373
ValueError: If SKILL.md is invalid or the skill name does not match
367374
the directory name.
368375
"""
369-
client = storage.Client()
376+
377+
client = storage.Client(project=project_id, credentials=credentials)
370378
bucket = client.bucket(bucket_name)
371379

372380
base_prefix = skills_base_path.strip("/")

src/google/adk/tools/bigquery/__init__.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,10 @@
2727
execute_sql can't arbitrarily mutate existing data.
2828
"""
2929

30-
import importlib
31-
import sys
30+
from .bigquery_credentials import BigQueryCredentialsConfig
31+
from .bigquery_toolset import BigQueryToolset
3232

33-
# Redirect this module to integrations/bigquery for backward compatibility.
34-
_TARGET = "google.adk.integrations.bigquery"
35-
36-
try:
37-
_mod = importlib.import_module(_TARGET)
38-
sys.modules[__name__] = _mod
39-
except ImportError:
40-
# Fallback for static analysis or if import fails during transition
41-
from google.adk.integrations.bigquery import BigQueryCredentialsConfig
42-
from google.adk.integrations.bigquery import BigQueryToolset
43-
from google.adk.integrations.bigquery import get_bigquery_skill
44-
45-
__all__ = [
46-
"BigQueryToolset",
47-
"BigQueryCredentialsConfig",
48-
"get_bigquery_skill",
49-
]
33+
__all__ = [
34+
"BigQueryToolset",
35+
"BigQueryCredentialsConfig",
36+
]

src/google/adk/integrations/bigquery/bigquery_credentials.py renamed to src/google/adk/tools/bigquery/bigquery_credentials.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from ...features import experimental
1818
from ...features import FeatureName
19-
from ...tools._google_credentials import BaseGoogleCredentialsConfig
19+
from .._google_credentials import BaseGoogleCredentialsConfig
2020

2121
BIGQUERY_TOKEN_CACHE_KEY = "bigquery_token_cache"
2222
BIGQUERY_SCOPES = [
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/google/adk/integrations/bigquery/data_insights_tool.py renamed to src/google/adk/tools/bigquery/data_insights_tool.py

File renamed without changes.

0 commit comments

Comments
 (0)