Skip to content

Commit 871a362

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix-thought-signature-pruning
2 parents b6b63e1 + 757ef22 commit 871a362

25 files changed

Lines changed: 1469 additions & 480 deletions
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "2.3.0"
2+
".": "2.4.0"
33
}

.github/release-please-config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,5 @@
5757
]
5858
}
5959
},
60-
"last-release-sha": "0cb4c814928f579bfbac9b9e1f95669e4304e089"
60+
"last-release-sha": "44d747ed5eaf543b5b8d22e0088f8a7c7eeee846"
6161
}

CHANGELOG.md

Lines changed: 180 additions & 0 deletions
Large diffs are not rendered by default.

src/google/adk/apps/compaction.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from __future__ import annotations
1616

1717
import logging
18+
from typing import AsyncGenerator
1819

1920
from google.genai import types
2021

@@ -443,7 +444,7 @@ async def _run_compaction_for_sliding_window(
443444
session_service: BaseSessionService,
444445
*,
445446
skip_token_compaction: bool = False,
446-
):
447+
) -> AsyncGenerator[Event, None]:
447448
"""Runs compaction for SlidingWindowCompactor.
448449
449450
This method implements the sliding window compaction logic. It determines
@@ -521,30 +522,35 @@ async def _run_compaction_for_sliding_window(
521522
Args:
522523
app: The application instance.
523524
session: The session containing events to compact.
524-
session_service: The session service for appending events.
525+
session_service: The session service, used by the token-threshold fallback.
525526
skip_token_compaction: Whether to skip token-threshold compaction.
527+
528+
Yields:
529+
The sliding-window compaction event, if one is produced. The caller (the
530+
runner loop) is responsible for appending it to the session, so that
531+
persistence of this event stays at the runtime's synchronization point.
526532
"""
527533
events = session.events
528534
if not events:
529-
return None
535+
return
530536

531537
config = app.events_compaction_config
532538
if config is None:
533-
return None
539+
return
534540

535541
# Prefer token-threshold compaction if configured and triggered.
536542
if not skip_token_compaction and _has_token_threshold_config(config):
537543
token_compacted = await _run_compaction_for_token_threshold(
538544
app, session, session_service
539545
)
540546
if token_compacted:
541-
return None
547+
return
542548

543549
if not _has_sliding_window_config(config):
544-
return None
550+
return
545551

546552
if config.compaction_interval is None or config.overlap_size is None:
547-
return None
553+
return
548554

549555
# Find the last compaction event and its range.
550556
last_compacted_end_timestamp = 0.0
@@ -573,7 +579,7 @@ async def _run_compaction_for_sliding_window(
573579
]
574580

575581
if len(new_invocation_ids) < config.compaction_interval:
576-
return None # Not enough new invocations to trigger compaction.
582+
return # Not enough new invocations to trigger compaction.
577583

578584
# Determine the range of invocations to compact.
579585
# The end of the compaction range is the last of the new invocations.
@@ -613,20 +619,20 @@ async def _run_compaction_for_sliding_window(
613619
events_to_compact = _longest_self_contained_prefix(events_to_compact)
614620

615621
if not events_to_compact:
616-
return None
622+
return
617623

618624
if app.root_agent is None:
619-
return None
625+
return
620626
_ensure_compaction_summarizer(config=config, agent=app.root_agent)
621627
if config.summarizer is None:
622-
return None
628+
return
623629

624630
compaction_event = await _summarize_events_with_trace(
625631
session=session,
626632
config=config,
627633
events_to_compact=events_to_compact,
628634
trigger='sliding_window',
629635
)
630-
if compaction_event:
631-
await session_service.append_event(session=session, event=compaction_event)
632636
logger.debug('Event compactor finished.')
637+
if compaction_event:
638+
yield compaction_event

src/google/adk/artifacts/artifact_util.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -134,32 +134,3 @@ def validate_artifact_reference_scope(
134134
"Session-scoped artifact references must stay within the same"
135135
" session scope."
136136
)
137-
138-
139-
def validate_path_segment(value: str, field_name: str) -> None:
140-
"""Rejects values that could alter the constructed path.
141-
142-
Args:
143-
value: The caller-supplied identifier (e.g. user_id or session_id).
144-
field_name: Human-readable name used in the error message.
145-
146-
Raises:
147-
InputValidationError: If the value contains path separators, traversal
148-
segments, or null bytes.
149-
"""
150-
if not value:
151-
raise input_validation_error.InputValidationError(
152-
f"{field_name} must not be empty."
153-
)
154-
if "\x00" in value:
155-
raise input_validation_error.InputValidationError(
156-
f"{field_name} must not contain null bytes."
157-
)
158-
if "/" in value or "\\" in value:
159-
raise input_validation_error.InputValidationError(
160-
f"{field_name} {value!r} must not contain path separators."
161-
)
162-
if value in (".", "..") or ".." in value.split("/"):
163-
raise input_validation_error.InputValidationError(
164-
f"{field_name} {value!r} must not contain traversal segments."
165-
)

src/google/adk/artifacts/file_artifact_service.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
from pydantic import ValidationError
3535
from typing_extensions import override
3636

37-
from . import artifact_util
3837
from ..errors.input_validation_error import InputValidationError
3938
from .base_artifact_service import ArtifactVersion
4039
from .base_artifact_service import BaseArtifactService
@@ -143,14 +142,39 @@ def _is_user_scoped(session_id: Optional[str], filename: str) -> bool:
143142
return session_id is None or _file_has_user_namespace(filename)
144143

145144

145+
def _validate_path_segment(value: str, field_name: str) -> None:
146+
"""Rejects values that could alter the constructed filesystem path.
147+
148+
Args:
149+
value: The caller-supplied identifier (e.g. user_id or session_id).
150+
field_name: Human-readable name used in the error message.
151+
152+
Raises:
153+
InputValidationError: If the value contains path separators, traversal
154+
segments, or null bytes.
155+
"""
156+
if not value:
157+
raise InputValidationError(f"{field_name} must not be empty.")
158+
if "\x00" in value:
159+
raise InputValidationError(f"{field_name} must not contain null bytes.")
160+
if "/" in value or "\\" in value:
161+
raise InputValidationError(
162+
f"{field_name} {value!r} must not contain path separators."
163+
)
164+
if value in (".", "..") or ".." in value.split("/"):
165+
raise InputValidationError(
166+
f"{field_name} {value!r} must not contain traversal segments."
167+
)
168+
169+
146170
def _user_artifacts_dir(base_root: Path) -> Path:
147171
"""Returns the path that stores user-scoped artifacts."""
148172
return base_root / "artifacts"
149173

150174

151175
def _session_artifacts_dir(base_root: Path, session_id: str) -> Path:
152176
"""Returns the path that stores session-scoped artifacts."""
153-
artifact_util.validate_path_segment(session_id, "session_id")
177+
_validate_path_segment(session_id, "session_id")
154178
return base_root / "sessions" / session_id / "artifacts"
155179

156180

@@ -232,7 +256,7 @@ def __init__(self, root_dir: Path | str):
232256

233257
def _base_root(self, user_id: str, /) -> Path:
234258
"""Returns the artifacts root directory for a user."""
235-
artifact_util.validate_path_segment(user_id, "user_id")
259+
_validate_path_segment(user_id, "user_id")
236260
return self.root_dir / "users" / user_id
237261

238262
def _scope_root(
@@ -245,7 +269,7 @@ def _scope_root(
245269
base = self._base_root(user_id)
246270
if _is_user_scoped(session_id, filename):
247271
return _user_artifacts_dir(base)
248-
if session_id is None:
272+
if not session_id:
249273
raise InputValidationError(
250274
"Session ID must be provided for session-scoped artifacts."
251275
)
@@ -519,7 +543,7 @@ def _list_artifact_keys_sync(
519543

520544
base_root = self._base_root(user_id)
521545

522-
if session_id is not None:
546+
if session_id:
523547
session_root = _session_artifacts_dir(base_root, session_id)
524548
for artifact_dir in _iter_artifact_dirs(session_root):
525549
metadata = self._latest_metadata(artifact_dir)

src/google/adk/artifacts/gcs_artifact_service.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,13 @@ def _get_blob_prefix(
167167
session_id: Optional[str] = None,
168168
) -> str:
169169
"""Constructs the blob name prefix in GCS for a given artifact."""
170-
artifact_util.validate_path_segment(app_name, "app_name")
171-
artifact_util.validate_path_segment(user_id, "user_id")
172170
if self._file_has_user_namespace(filename):
173171
return f"{app_name}/{user_id}/user/{filename}"
174172

175173
if session_id is None:
176174
raise InputValidationError(
177175
"Session ID must be provided for session-scoped artifacts."
178176
)
179-
artifact_util.validate_path_segment(session_id, "session_id")
180177
return f"{app_name}/{user_id}/{session_id}/{filename}"
181178

182179
def _get_blob_name(
@@ -371,10 +368,6 @@ def _load_artifact(
371368
def _list_artifact_keys(
372369
self, app_name: str, user_id: str, session_id: Optional[str]
373370
) -> list[str]:
374-
artifact_util.validate_path_segment(app_name, "app_name")
375-
artifact_util.validate_path_segment(user_id, "user_id")
376-
if session_id is not None:
377-
artifact_util.validate_path_segment(session_id, "session_id")
378371
filenames = set()
379372

380373
if session_id:

src/google/adk/artifacts/in_memory_artifact_service.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,13 @@ def _artifact_path(
8585
Returns:
8686
The constructed artifact path.
8787
"""
88-
artifact_util.validate_path_segment(app_name, "app_name")
89-
artifact_util.validate_path_segment(user_id, "user_id")
9088
if self._file_has_user_namespace(filename):
9189
return f"{app_name}/{user_id}/user/{filename}"
9290

9391
if session_id is None:
9492
raise InputValidationError(
9593
"Session ID must be provided for session-scoped artifacts."
9694
)
97-
artifact_util.validate_path_segment(session_id, "session_id")
9895
return f"{app_name}/{user_id}/{session_id}/{filename}"
9996

10097
@override
@@ -218,10 +215,6 @@ async def load_artifact(
218215
async def list_artifact_keys(
219216
self, *, app_name: str, user_id: str, session_id: Optional[str] = None
220217
) -> list[str]:
221-
artifact_util.validate_path_segment(app_name, "app_name")
222-
artifact_util.validate_path_segment(user_id, "user_id")
223-
if session_id is not None:
224-
artifact_util.validate_path_segment(session_id, "session_id")
225218
usernamespace_prefix = f"{app_name}/{user_id}/user/"
226219
session_prefix = (
227220
f"{app_name}/{user_id}/{session_id}/" if session_id else None

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@
2323
from typing import Optional
2424
from typing import Tuple
2525

26+
from google.adk.utils import _mtls_utils
2627
import google.auth
2728
import google.auth.exceptions
2829
from google.auth.transport.requests import AuthorizedSession
2930
from google.auth.transport.requests import Request
3031
import requests
3132

3233
_VERTEX_AI_ENDPOINT = "https://{location}-aiplatform.googleapis.com/v1beta1"
34+
_VERTEX_AI_MTLS_ENDPOINT = (
35+
"https://{location}-aiplatform.mtls.googleapis.com/v1beta1"
36+
)
3337

3438

3539
def check_adc() -> bool:
@@ -75,7 +79,18 @@ def _call_vertex_express_api(
7579
"""Calls a Vertex AI Express API."""
7680
credentials, _ = google.auth.default()
7781
session = AuthorizedSession(credentials)
78-
url = f"{_VERTEX_AI_ENDPOINT.format(location=location)}/vertexExpress{action}"
82+
83+
if _mtls_utils.use_client_cert_effective():
84+
session.configure_mtls_channel()
85+
endpoint = _mtls_utils.get_api_endpoint(
86+
location=location,
87+
default_template=_VERTEX_AI_ENDPOINT,
88+
mtls_template=_VERTEX_AI_MTLS_ENDPOINT,
89+
)
90+
else:
91+
endpoint = _VERTEX_AI_ENDPOINT.format(location=location)
92+
93+
url = f"{endpoint}/vertexExpress{action}"
7994
headers = {
8095
"Content-Type": "application/json",
8196
}

0 commit comments

Comments
 (0)