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
1 change: 0 additions & 1 deletion contributing/samples/gepa/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
from tau_bench.types import EnvRunResult
from tau_bench.types import RunConfig
import tau_bench_agent as tau_bench_agent_lib

import utils


Expand Down
1 change: 0 additions & 1 deletion contributing/samples/gepa/run_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from absl import flags
import experiment
from google.genai import types

import utils

_OUTPUT_DIR = flags.DEFINE_string(
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ test = [
"a2a-sdk>=0.3.0,<0.4.0",
"anthropic>=0.43.0", # For anthropic model tests
"crewai[tools];python_version>='3.11' and python_version<'3.12'", # For CrewaiTool tests; chromadb/pypika fail on 3.12+
"google-cloud-iamconnectorcredentials>=0.1.0, <0.2.0",
"google-cloud-parametermanager>=0.4.0, <1.0.0",
"kubernetes>=29.0.0", # For GkeCodeExecutor
"langchain-community>=0.3.17",
Expand Down Expand Up @@ -176,6 +177,10 @@ toolbox = ["toolbox-adk>=1.0.0, <2.0.0"]

slack = ["slack-bolt>=1.22.0"]

agent-identity = [
"google-cloud-iamconnectorcredentials>=0.1.0, <0.2.0",
]

[tool.pyink]
# Format py files following Google style-guide
line-length = 80
Expand Down
27 changes: 27 additions & 0 deletions src/google/adk/artifacts/file_artifact_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,39 @@ def _is_user_scoped(session_id: Optional[str], filename: str) -> bool:
return session_id is None or _file_has_user_namespace(filename)


def _validate_path_segment(value: str, field_name: str) -> None:
"""Rejects values that could alter the constructed filesystem path.

Args:
value: The caller-supplied identifier (e.g. user_id or session_id).
field_name: Human-readable name used in the error message.

Raises:
InputValidationError: If the value contains path separators, traversal
segments, or null bytes.
"""
if not value:
raise InputValidationError(f"{field_name} must not be empty.")
if "\x00" in value:
raise InputValidationError(f"{field_name} must not contain null bytes.")
if "/" in value or "\\" in value:
raise InputValidationError(
f"{field_name} {value!r} must not contain path separators."
)
if value in (".", "..") or ".." in value.split("/"):
raise InputValidationError(
f"{field_name} {value!r} must not contain traversal segments."
)


def _user_artifacts_dir(base_root: Path) -> Path:
"""Returns the path that stores user-scoped artifacts."""
return base_root / "artifacts"


def _session_artifacts_dir(base_root: Path, session_id: str) -> Path:
"""Returns the path that stores session-scoped artifacts."""
_validate_path_segment(session_id, "session_id")
return base_root / "sessions" / session_id / "artifacts"


Expand Down Expand Up @@ -220,6 +246,7 @@ def __init__(self, root_dir: Path | str):

def _base_root(self, user_id: str, /) -> Path:
"""Returns the artifacts root directory for a user."""
_validate_path_segment(user_id, "user_id")
return self.root_dir / "users" / user_id

def _scope_root(
Expand Down
20 changes: 20 additions & 0 deletions src/google/adk/cli/cli_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,24 @@ def to_cloud_run(
shutil.rmtree(temp_folder)


def _print_agent_engine_url(resource_name: str) -> None:
"""Prints the Google Cloud Console URL for the deployed agent."""
parts = resource_name.split('/')
if len(parts) >= 6 and parts[0] == 'projects' and parts[2] == 'locations':
project_id = parts[1]
region = parts[3]
engine_id = parts[5]

url = (
'https://console.cloud.google.com/agent-platform/runtimes'
f'/locations/{region}/agent-engines/{engine_id}/playground'
f'?project={project_id}'
)
click.secho(
f'\n🎉 View your deployed agent here:\n{url}\n', fg='cyan', bold=True
)


def to_agent_engine(
*,
agent_folder: str,
Expand Down Expand Up @@ -1150,11 +1168,13 @@ def to_agent_engine(
f'✅ Created agent engine: {agent_engine.api_resource.name}',
fg='green',
)
_print_agent_engine_url(agent_engine.api_resource.name)
else:
if project and region and not agent_engine_id.startswith('projects/'):
agent_engine_id = f'projects/{project}/locations/{region}/reasoningEngines/{agent_engine_id}'
client.agent_engines.update(name=agent_engine_id, config=agent_config)
click.secho(f'✅ Updated agent engine: {agent_engine_id}', fg='green')
_print_agent_engine_url(agent_engine_id)
finally:
click.echo(f'Cleaning up the temp folder: {temp_folder}')
shutil.rmtree(agent_src_path)
Expand Down
14 changes: 13 additions & 1 deletion src/google/adk/evaluation/evaluation_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def convert_events_to_eval_invocations(
for invocation_id, events in events_by_invocation_id.items():
final_response = None
final_event = None
user_content = Content(parts=[])
user_content = None
invocation_timestamp = 0
app_details = None
if (
Expand Down Expand Up @@ -312,6 +312,18 @@ def convert_events_to_eval_invocations(
events_to_add.append(event)
break

if user_content is None:
# Skip invocations that have no user-authored event. Such invocations
# arise from internal/system-driven turns (e.g. background agent tasks)
# and are not meaningful for evaluation purposes. Including them would
# also cause a Pydantic ValidationError because Invocation.user_content
# requires a Content object.
logger.debug(
"Skipping invocation %s: no user-authored event found.",
invocation_id,
)
continue

invocation_events = [
InvocationEvent(author=e.author, content=e.content)
for e in events_to_add
Expand Down
35 changes: 35 additions & 0 deletions src/google/adk/integrations/agent_identity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# GCP IAM Connector Auth

Manages the complete lifecycle of an access token using the Google Cloud
Platform Agent Identity Credentials service.

## Usage

1. **Install Dependencies:**
```bash
pip install "google-adk[agent-identity]"
```

2. **Register the provider:**
Register the `GcpAuthProvider` with the `CredentialManager`. This is to be
done one time.

``` py
# user_agent_app.py
from google.adk.auth.credential_manager import CredentialManager
from google.adk.integrations.agent_identity import GcpAuthProvider

CredentialManager.register_auth_provider(GcpAuthProvider())
```

3. **Configure the Auth provider:**
Specify the Agent Identity provider configuration using the
`GcpAuthProviderScheme`.
``` py
# user_agent_app.py
from google.adk.integrations.agent_identity import GcpAuthProviderScheme

# Configures Toolset
auth_scheme = GcpAuthProviderScheme(name="my-jira-auth_provider")
mcp_toolset_jira = McpToolset(..., auth_scheme=auth_scheme)
```
21 changes: 21 additions & 0 deletions src/google/adk/integrations/agent_identity/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2026 Google LLC
#
# 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.

from .gcp_auth_provider import GcpAuthProvider
from .gcp_auth_provider_scheme import GcpAuthProviderScheme

__all__ = [
"GcpAuthProvider",
"GcpAuthProviderScheme",
]
Loading