The dev-server test-run endpoint spawned pytest inside a fire-and-forget
asyncio.create_task() and piped its output through an unbounded asyncio.Queue.
Nothing owned that task, so a client that disconnected mid-run left pytest, its
descendants, and the output pump running until the server itself exited, and
the queue could grow without bound while no consumer was draining it.
The response iterator now owns the subprocess for its whole lifetime: it spawns
pytest, reads bounded chunks straight off the pipe so the client applies
natural backpressure, and terminates the process tree in a finally block.
Termination reaches descendants rather than just the direct child - on POSIX
pytest is started as its own process-group leader and signalled with os.killpg,
and on Windows it runs in a new process group torn down with taskkill /T.
Cleanup escalates from a graceful signal to a forced kill after a bounded wait,
and falls back to signalling the direct child if the process group turns out
not to exist.
Cleanup runs under an anyio shield, so the cancel scope the server cancels on
client disconnect cannot interrupt it partway. The shield covers the common
case, where the disconnect arrives while the iterator is parked reading pytest
output or awaiting process exit. It is not a guarantee on every path: if the
disconnect lands while the iterator is suspended at a yield, the async
generator is dropped rather than cancelled, and its finally block runs at
async-generator finalization instead. That finalization does happen under
CPython, but its timing is not deterministic.
Behavior change: disconnecting from the test-output stream now aborts the
in-flight pytest run. Previously the run continued to completion in the
background after the client went away. Nothing persists the result of a run -
the output is only streamed - so a background completion was unobservable, but
a caller that relied on starting a run and hanging up must now keep the
response stream open until it ends. The endpoint path, its parameters, and the
streamed byte content are unchanged.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970107514
Merge https://github.com/google/adk-python/pull/6701
## Summary
Implements **Cloud Build private worker pool** support for `adk deploy agent_engine` ([#2141](https://github.com/google/adk-python/issues/2141)).
### Why this is needed
Enterprise / VPC-SC Agent Engine deploys often **cannot use the default public Cloud Build pool**. Without a way to point the build at a private worker pool, `adk deploy agent_engine` fails for teams that require private networking, org build policies, or connectivity to private resources.
The Vertex Agent Engine SDK already accepts this via `config.build_config.worker_pool` → `spec.build_spec.worker_pool`, but ADK never exposed it on the CLI or documented a first-class config key.
### What we did
- Added `--worker_pool` to `adk deploy agent_engine`
- Accepted the same value from `.agent_engine_config.json` as either:
- top-level `"worker_pool": "projects/.../workerPools/..."` (convenience), or
- `"build_config": {"worker_pool": "..."}` (native SDK shape)
- Nested the value into `agent_config["build_config"]["worker_pool"]` before `client.agent_engines.update(...)`
- Validated the Cloud Build resource name format early with a clear error
- Preserved other `build_config` fields (e.g. build `service_account`)
- CLI flag overrides config-file values (same pattern as `display_name`)
### How it fits
```
adk deploy agent_engine --worker_pool=...
│
▼
to_agent_engine(... worker_pool=...)
│
▼
agent_config["build_config"]["worker_pool"] = <resource name>
│
▼
vertexai.Client().agent_engines.update(config=agent_config)
│
▼
Cloud Build runs on the private worker pool
```
### Usage
```bash
adk deploy agent_engine \
--project=my-project \
--region=us-central1 \
--worker_pool=projects/my-project/locations/us-central1/workerPools/my-private-pool \
my_agent
```
Or in `.agent_engine_config.json`:
```json
{
"worker_pool": "projects/my-project/locations/us-central1/workerPools/my-private-pool"
}
```
### Verification
- Confirmed `worker_pool` was **not** previously implemented in ADK (`rg` / deploy path audit)
- Confirmed Vertex SDK mapping in `vertexai/_genai/agent_engines.py` (`build_config.worker_pool` → `spec.build_spec.worker_pool`)
- Added unit tests for validation, config nesting, CLI passthrough, and deploy config forwarding
- `uv run pytest tests/unittests/cli/utils/test_cli_deploy.py` → **60 passed**
Fixes#2141
## Test plan
- [x] Unit tests for `_validate_worker_pool` (valid + malformed)
- [x] Unit tests for `_apply_worker_pool_to_agent_config` (CLI, config top-level, override, preserve other build_config fields)
- [x] `to_agent_engine` forwards `build_config.worker_pool` on `agent_engines.update`
- [x] CLI `--worker_pool` reaches `to_agent_engine`
- [ ] Maintainer review of CLI naming / config-file shape
- [ ] Optional: end-to-end deploy against a real private worker pool in a VPC-SC project
---
cc @klateefa @yeesian @wuliang229 @Jacksunwei @hangfei @llalitkumarrr @GWeale
I claimed this on [#2141](https://github.com/google/adk-python/issues/2141#issuecomment-5274468133) (self-assign needs triage permissions — please assign me `@a2105z` if that helps routing). Ready for review whenever you have a moment.
Co-authored-by: Yifan Wang <wanyif@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6701 from a2105z:feat/agent-engine-worker-pool c59661ae1c2e244037e85f5b0a577b9cff70197b
PiperOrigin-RevId: 968673077
Merge https://github.com/google/adk-python/pull/6550
### Link to Issue or Description of Change
**1. Link to an existing issue (if applicable):**
N/A
**2. Or, if no issue exists, describe the change:**
**Problem:**
`adk test --rebuild`, `dev_server.py` (ADK Web test creation), and `cli.py` (JSONL stream output) write/output JSON using `json.dump` / `json.dumps` with the default `ensure_ascii=True` and without explicit UTF-8 encoding.
As a result, Japanese and other non-ASCII event text is converted to `\uXXXX` escape sequences, making test fixtures and CLI outputs difficult to read and review.
**Solution:**
- Write rebuilt and saved test fixtures as UTF-8 with `ensure_ascii=False`.
- Output CLI JSONL events and schemas with `ensure_ascii=False`.
- Add regression unit tests verifying non-ASCII text preservation in rebuilt tests, web server test creation, and CLI event printing.
### Testing Plan
**Unit Tests:**
- [x] I have added or updated unit tests for my change.
- [x] All relevant unit tests pass locally.
### Checklist
- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing relevant unit tests pass locally with my changes.
- [ ] I have manually tested my changes end-to-end.
- [x] Any dependent changes have been merged and published in downstream modules. (N/A: no dependent changes.)
### Additional context
No public APIs or fixture schemas are changed. Rebuilt files remain JSON-compatible; only the textual representation of non-ASCII characters changes.
Co-authored-by: Yi Liu <yiliuly@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6550 from ftnext:preserve-unicode-in-rebuilt-tests c9a1197c7c5ded4afed86279acfcc2025135fd9c
PiperOrigin-RevId: 967917608
This prevents Click from expanding wildcard arguments on Windows (e.g. `*` in `--allow_origins "*"`), avoiding errors when Click attempts to parse expanded filenames as unexpected positional arguments.
Fixes#6248
PiperOrigin-RevId: 966756317
On Windows, files inside `.git/objects/` are marked read-only by default. When `adk deploy` cleans up the temporary directory in its `finally` block, `shutil.rmtree()` raises `PermissionError: [WinError 5] Access denied` on these files, causing the CLI to crash even after a successful deployment.
This PR introduces a `_robust_rmtree()` helper that passes an error handler to `shutil.rmtree`. On Windows, the handler clears the read-only bit (`os.chmod(path, stat.S_IWRITE)`) and retries the deletion. On non-Windows platforms, `shutil.rmtree` is called without any handler. The fix uses `onexc` for Python >= 3.12 and `onerror` for Python < 3.12 to avoid deprecation warnings.
All six `shutil.rmtree()` call sites in `cli_deploy.py` (`to_cloud_run`, `to_agent_engine`, `to_gke`) are updated.
Fixes#4635
Merge https://github.com/google/adk-python/pull/4719
PiperOrigin-RevId: 964392331
_OriginCheckMiddleware returned early for GET/HEAD/OPTIONS, and again whenever a
request carried no Origin, so only writes were validated. Every read endpoint of
`adk web` / `adk api_server` was served to a page that reached the server by
rebinding a hostname to 127.0.0.1.
Origin cannot close that, because browsers omit it on requests they consider
same-origin, as they do a rebound page's. So check Host on every request: a
loopback bind is reachable only from this machine, so a request naming any other
host was pointed here by rebound DNS. Only the real Host header counts, since it
is a forbidden request header whereas a same-origin fetch() can set
X-Forwarded-Host or Forwarded freely. The safe-method exemption is gone, and the
/run_live handshake gets the same check.
Serving another hostname from a loopback bind now means naming it in
--allow_origins, which vouches for that origin's host rather than switching the
guard off; only "*" opts out entirely.
Both checks key off the bind address instead of scope["server"] - ASGI servers
fill that from the accepted socket, so a --host=0.0.0.0 server reports 127.0.0.1
for any loopback connection and looked local-only behind a same-machine proxy.
The bind arrives through a new get_fast_api_app(bind_host=...) that the CLI
passes. The existing host parameter keeps its 127.0.0.1 default and its meaning:
an embedder that binds elsewhere without passing it would otherwise have the
guard keyed to a loopback bind it does not have, and reject all of its own
traffic.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 964277197
Merge https://github.com/google/adk-python/pull/5402
## Summary
- normalize Pub/Sub subscription and Eventarc source metadata before reusing them as session user ids
- replace slash-separated resource paths with path-safe -- delimiters while preserving the full resource identity
- add trigger endpoint regression tests that verify the created sessions are stored under the normalized user ids
## Testing
- python3 -m py_compile src/google/adk/cli/trigger_routes.py tests/unittests/cli/test_trigger_routes.py
- python3 -m pytest tests/unittests/cli/test_trigger_routes.py -k "path_safe or with_subscription_metadata or source_from_ce_header" (fails during collection in this environment: ModuleNotFoundError: No module named 'fastapi')
Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5402 from MukundaKatta:codex/trigger-user-id-path-safe a97467903bcadc92fc7a916ccef95638c58b3a65
PiperOrigin-RevId: 963505905
Merge https://github.com/google/adk-python/pull/6114
## Link to Issue or Description of Change
Closes : #6093
**Problem:**
On Agent Engine deployments served by the ADK API server, every call to the
`/api/stream_reasoning_engine` route with a synchronous streaming `class_method`
(e.g. `stream_query`) ends with `RuntimeError: coroutine raised StopIteration`
after the last chunk is streamed.
The cause is the sync-to-async adapter `_aiter_from_iter` in
`src/google/adk/cli/fast_api.py` (lines 916–922 in v2.2.0):
async def _aiter_from_iter(iterator):
while True:
try:
chunk = await run_in_threadpool(next, iterator)
yield chunk
except StopIteration:
break
The `except StopIteration` is unreachable. When the iterator is exhausted,
`next()` raises `StopIteration` inside the worker thread, anyio sets it on a
future, and it propagates out of the `run_in_threadpool` coroutine frame.
Python (PEP 479) forbids `StopIteration` escaping a coroutine and converts it
to `RuntimeError("coroutine raised StopIteration")` before the `except` clause
ever sees it.
**Affected versions:** Regression introduced in v2.2.0 — the route and the
buggy adapter were added in the same commit. Not present in the v1.x line
(verified absent at v1.35.0).
**Solution:**
Stop relying on `StopIteration` crossing the await boundary; use a sentinel
default so iterator exhaustion never raises across it:
_SENTINEL = object()
async def _aiter_from_iter(iterator):
while True:
chunk = await run_in_threadpool(next, iterator, _SENTINEL)
if chunk is _SENTINEL:
break
yield chunk
This is the minimal, idiomatic fix; the stream now terminates cleanly when the
sync generator is exhausted.
## Testing Plan
**Unit Tests:**
- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.
Added `test_gemini_stream_reasoning_engine_sync_generator` plus a
`test_app_with_gemini_enterprise_sync_stream` fixture in
`tests/unittests/cli/test_fast_api.py`. The pre-existing stream test used an
*async* generator (the `isasyncgenfunction` branch) and never exercised the
buggy sync-generator path. The new test fails on the unpatched code with
`RuntimeError` and passes with the fix.
pytest summary:
$ pytest tests/unittests/cli/test_fast_api.py -k stream_reasoning_engine -q
3 passed, 79 deselected
$ pytest tests/unittests/cli/test_fast_api.py -q
82 passed
**Manual End-to-End (E2E) Tests:**
The failure and the fix reproduce standalone in ~15 lines, independent of any
model or deployment:
import asyncio
from starlette.concurrency import run_in_threadpool
async def _aiter_from_iter(iterator): # old, buggy version
while True:
try:
chunk = await run_in_threadpool(next, iterator)
yield chunk
except StopIteration:
break
async def main():
def gen():
yield 1
yield 2
async for c in _aiter_from_iter(gen()):
print("chunk:", c)
asyncio.run(main())
# chunk: 1
# chunk: 2
# RuntimeError: coroutine raised StopIteration <-- before the fix
With the sentinel version above, the same script prints the two chunks and
exits cleanly with no exception. Originally observed on a live Vertex AI Agent
Engine deployment (google-adk==2.2.0, Python 3.11) where every `stream_query`
call logged the RuntimeError after the final chunk.
## Checklist
- [x] I have read the CONTRIBUTING.md document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [x] I have manually tested my changes end-to-end.
- [ ] Any dependent changes have been merged and published in downstream modules.
## Additional context
Original server traceback:
ERROR: Exception in ASGI application
Traceback (most recent call last):
File ".../starlette/responses.py", line 250, in stream_response
async for chunk in self.body_iterator:
File ".../google/adk/cli/fast_api.py", line 797, in json_generator
async for chunk in output:
File ".../google/adk/cli/fast_api.py", line 919, in _aiter_from_iter
chunk = await run_in_threadpool(next, iterator)
File ".../starlette/concurrency.py", line 32, in run_in_threadpool
return await anyio.to_thread.run_sync(func)
File ".../anyio/to_thread.py", line 63, in run_sync
return await get_async_backend().run_sync_in_worker_thread(
File ".../anyio/_backends/_asyncio.py", line 2518, in run_sync_in_worker_thread
return await future
RuntimeError: coroutine raised StopIteration
Occurs 100% of the time on every sync streaming request once the generator is
exhausted. The bug is model-agnostic (purely in the FastAPI streaming adapter).
Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6114 from surajit-1306:fix/stream-reasoning-engine-stopiteration e5ee866074fefc56418ec03441e3706617f9d755
PiperOrigin-RevId: 962875380
Track user choices (e.g. CREATE_EXPRESS, MANUAL_PROJECT, ABANDON) during Express Mode onboarding in ADK CLI telemetry logs.
- Added express_mode_action field to CliCommandRun proto schema.
- Recorded express_mode_action in MetricsCollector and forwarded from Click context metadata during command execution.
- Added unit tests for express_mode_action serialization.
- Updated Clearcut route test case ADK_CLI_basic.textpb.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 962278596
Merge https://github.com/google/adk-python/pull/4412
**Problem:**
`adk eval` behavior was inconsistent with the expected config discovery flow.
When `--config_file_path` was omitted, CLI always fell back to default criteria,
instead of using `test_config.json` located next to an eval file. This differs
from `AgentEvaluator.evaluate`, which already discovers a `test_config.json`
sitting next to each test file.
**Solution:**
Added config path resolution in `adk eval`:
- If `--config_file_path` is provided, use it as-is.
- If omitted and input is a single eval file, look for
`<eval_file_dir>/test_config.json`.
- If omitted and input is multiple eval files or eval set IDs, do not
auto-discover and keep default criteria behavior.
If no adjacent `test_config.json` is found, behavior is unchanged and the
built-in default evaluation criteria are used.
This keeps behavior explicit for mixed-directory multi-file runs while enabling
convenient per-file config discovery for single-file usage. Auto-discovery is
intentionally limited to single-file input to avoid ambiguous behavior when
multiple eval files are provided from different directories.
Towards #4410
Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 961863867
Merge https://github.com/google/adk-python/pull/6001
## Summary
Fixes#5995.
`create_empty_state()` only walked `sub_agents`, so graph-based `Workflow` roots crashed when the dev server tried to add the current session to an eval set. Workflow children live in `workflow.graph.nodes`, not `sub_agents`.
This updates the state traversal to:
- keep the existing `sub_agents` walk for normal agents
- also walk `graph.nodes` for Workflow-style roots and nested graph nodes
- track visited objects so shared graph/agent nodes are not processed repeatedly
## Testing
```
python -m pytest tests\unittests\cli\utils\test_state.py -q
python -m py_compile src\google\adk\cli\utils\state.py tests\unittests\cli\utils\test_state.py
python -m pyink --check src\google\adk\cli\utils\state.py tests\unittests\cli\utils\test_state.py
git diff --check
```
Co-authored-by: Yi Liu <yiliuly@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6001 from he-yufeng:fix/workflow-empty-state 1bc442a5508cc527cbf364d36dfbf90d1c762dec
PiperOrigin-RevId: 960722840
- Reverts recording telemetry duration early at server startup for adk web and api_server commands so duration spans from command invocation until server termination.
- Sets a server_started flag in context metadata when web or api_server completes startup in its lifespan hook.
- Updates TelemetryGroup.invoke to treat a KeyboardInterrupt after successful startup as a clean exit (exit code 0, no error logged) while still recording an error if KeyboardInterrupt occurs before startup completes.
- Adds unit tests for post-startup KeyboardInterrupt clean exit and non-interrupt exception error recording.
Co-authored-by: Lucas Kang <lucaskang@google.com>
PiperOrigin-RevId: 959915871
test_telemetry_cli_commands asserted that `adk telemetry` with no subcommand
exits 0. That holds on click 8.1.x, but click >= 8.2 treats a group invoked
without a subcommand as a usage error and exits 2. pyproject.toml allows
click>=8.1.8,<9, so the test has been failing on every CI run that resolves a
recent click.
The point of the assertion is that help is printed, so assert on the help output
and accept either exit code.
Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 959238713
- Detect whether standard output is connected to an interactive terminal by registering a new is_tty dimension in the collector environment schema.
- Allows filtering and analyzing human user sessions separately from automated scripts, cron jobs, and CI/CD pipelines
Co-authored-by: Lucas Kang <lucaskang@google.com>
PiperOrigin-RevId: 957254560
- Refactors the telemetry tracking in TelemetryGroup to support early recording for adk web and adk api_server.
- Server duration was previously tied to the total time the server was online until termination, and an intentional Ctrl+C termination would falsely log a KeyboardInterrupt crash.
- Servers can manually dispatch telemetry with precise startup times and exit statuses, ensuring accurate startup profiling metrics while eliminating false-positive crash alerts on routine teardown.
- Adds unit tests verifying early-logging safety nets and exception bubbling.
Co-authored-by: Lucas Kang <lucaskang@google.com>
PiperOrigin-RevId: 957214033
Importing google.adk eagerly pulled in Agent, Runner, Workflow, and the server
and CLI runtimes even for callers that used none of them, and google-genai
imported the MCP client and FastMCP server stack whenever MCP happened to be
installed. The package, agents, workflow, cli, and cli.utils namespaces now
resolve their exports lazily on first use (PEP 562) through a shared
google.adk.utils._lazy helper. Importing google.adk drops from roughly 2.1s to
a few ms.
Public APIs and object identities are unchanged, with two things to note when
upgrading:
* The google-genai floor moves from 2.9 to 2.12.1, the release that defers MCP
itself. Environments pinned below 2.12.1 will fail to resolve.
* google.adk.cli.utils no longer re-exports BaseAgent and LlmAgent. They were
unused eager imports, never part of that module's __all__; import them from
google.adk.agents instead.
Lazy resolution moves failures from import time to first use, so a missing or
broken optional dependency now surfaces on the first request rather than at
process start. Long-running servers pay the one-time resolution cost on their
first request; a warmup hook is deliberately left to a follow-up so this change
adds no public API.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 956721154
Introduce logic to group sequential ADK CLI execution logs under a single logical tracking session if they are launched within the same terminal shell session and within a 1-hour activity window.
Key changes:
- Associate session tracking with the parent process ID (PPID) of the launching terminal shell.
- Manage sessions locally under a sessions storage file.
- Prune active session records idle for more than an hour to prevent size growth.
- Add comprehensive suite of unit tests verifying metrics collection, sequence tracking, and pruning behaviors.
Co-authored-by: Lucas Kang <lucaskang@google.com>
PiperOrigin-RevId: 956189218
- Wrap main Click group execution with a custom TelemetryGroup class to track CLI execution metrics.
- Record command name, subcommand, flags, duration, exit code, and exception type when consent is enabled.
- Exclude 'telemetry' command from metrics logging.
Co-authored-by: Lucas Kang <lucaskang@google.com>
PiperOrigin-RevId: 956018260
- Prompts the user during their first interactive CLI subcommand execution to opt in to anonymized telemetry tracking.
- Implements telemetry subcommand group with enable, disable, and status actions to change settings persistently via ~/.adk/config.json.
- Gracefully handles KeyboardInterrupt and EOFError: defaults preference to off for the current session without saving to disk.
- Differentiates unconfigured default-off state from explicitly disabled state in status outputs.
- Adds comprehensive unit tests validating prompts, interrupt triggers, status commands, and preference storage.
Co-authored-by: Lucas Kang <lucaskang@google.com>
PiperOrigin-RevId: 955514787
Introduce user-opt-in telemetry tracking to collect command run metrics, durations, and environment details. Telemetry requests are processed in an asynchronous background daemon process, protecting against execution latency. Includes backoff rate-limiting compliance to prevent server-side DoS conditions.
Co-authored-by: Lucas Kang <lucaskang@google.com>
PiperOrigin-RevId: 955460451
The agent_engine deploy path only uploaded a fixed set of source
packages, so users could not ship extra local libraries alongside their
agent. Add a repeatable `--extra_packages` option (also settable via an
`extra_packages` key in the agent platform config file) that stages each
given file or directory into the build context, appends it to
source_packages, and copies it into the image with `/app` prepended to
PYTHONPATH so it is importable at runtime.
Close#3936
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955429600
Pre-emptive: every CI job is ubuntu-latest, so none of these tests fail today.
No assertion is weakened - each replacement is equivalent or stricter on Linux.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 954835278
FileArtifactService stored every artifact under `root/users/{user_id}`,
dropping app_name from the path entirely. Two apps served from one root
therefore shared a single artifact namespace: saving `report.txt` from one app
overwrote the other app's `report.txt`, and load, list, delete and the version
APIs all returned the other app's data. The in-memory and GCS services already
key on app_name, so the file service was the odd one out.
app_name is now threaded through all seven public methods, and artifacts live
under `root/apps/{app_name}/users/{user_id}/...` to match the other services.
app_name is validated as a path segment the way user_id and session_id already
are, so the file service now runs the same traversal tests as the other two
services.
The pre-app-scoped `root/users` tree is still read when the app-scoped location
holds nothing. That is not only for in-place upgrades: the CLI's per-agent
artifact storage already ships a fallback that points a FileArtifactService at
the shared `.adk/artifacts` folder, whose entire contents are in the
pre-app-scoped layout, so dropping the read would silently break an existing
migration path. Saves only ever go to the app-scoped location, and deleting an
artifact removes both copies.
Backward-compatibility notes, both limited to data written before this change:
- Artifacts already under `root/users` stay readable by every app sharing that
root, and a delete from any one of those apps removes them for all of them,
because that layout records no owner. Isolation is complete for artifacts
written from this release onwards. The CLI is unaffected: it gives each agent
its own root, so a given root's `root/users` tree only ever held one agent's
artifacts.
- The first save of such an artifact restarts version numbering in the
app-scoped location and stops serving the older versions, which stay on disk
until the artifact is deleted.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 954787418
Merge https://github.com/google/adk-python/pull/6458
Live/audio agent eval was only exercisable through private internal service imports; the public surface (CLI, dev-server, AgentEvaluator) always ran non-live text inference, so users had no supported path to evaluate Live API agents with a simulated audio user.
This threads `use_live` through all three public entrypoints, fixes the live-send path so native-audio models accept simulated user audio, and lets the dev-server select an audio (`llm_audio`) user simulator over HTTP. Live transcriptions are consolidated to text, with the text response preferred as the gradable output for turns carrying both audio and a transcript.
Adds a runnable sample (`live_non_blocking_tool_agent` evalset + `test_config` with `use_live: true` and a Gemini TTS audio simulator) plus unit tests covering `use_live` propagation, request validation, resampling, and the realtime-audio send path.
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6458 from allen-stephen:feat/live-eval-parity 3fc33a2616d1515c822387deb8d70c27d5bc6244
PiperOrigin-RevId: 954725627
When `adk web` is given a path to a single agent directory (containing agent.py or root_agent.yaml), preserve single-agent mode even if that directory contains subfolders.
Fixes https://github.com/google/adk-python/issues/6434
Co-authored-by: Bo Yang <ybo@google.com>
PiperOrigin-RevId: 953470819
Updates dot_adk_folder_for_agent to resolve app names containing dots (e.g., "parent.child") to nested subdirectories (e.g., "parent/child") instead of a single flat directory. This ensures that local storage and session databases are correctly located for nested agents
Co-authored-by: Yifan Wang <wanyif@google.com>
PiperOrigin-RevId: 952905400
- Establish read_telemetry_consent and write_telemetry_consent utilities to store opt-in status locally in ~/.adk/config.json.
- Implement GET and POST '/config/telemetry' FastAPI endpoints inside dev_server.py.
- Prevent CSRF/XSRF forgery by requiring the 'x-adk-telemetry-request: true' header on all POST requests.
- Add unit tests verifying route access permissions and json persistence.
Co-authored-by: Lucas Kang <lucaskang@google.com>
PiperOrigin-RevId: 952873455
Merge https://github.com/google/adk-python/pull/6161
Defer the missing-rubrics failure until the effective rubric list is built, after invocation rubrics have been merged. Both rubric-based prompt formatters now read rubrics through get_effective_rubrics_list(). CLI pretty printing now treats missing criterion rubrics as an empty lookup and falls back to the rubric id.
PiperOrigin-RevId: 952442674
Several paths misbehaved when ADK was imported or run on Windows: drive-letter paths were split at the wrong colon (including for paths that did not exist yet), text writes could rewrite explicit newlines, telemetry detection assumed POSIX separators, and the Bash tool imported the POSIX-only resource module at import time. This splits eval selectors from the right while preserving drive paths, writes text with exact newline handling, normalizes telemetry paths before matching, and imports the Bash tool safely on Windows with an explicit POSIX-only error after confirmation.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 952417974
Merge https://github.com/google/adk-python/pull/4413
**Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) before creating a pull request.**
### Link to Issue or Description of Change
**1. Link to an existing issue (if applicable):**
- Related: #4410
**Problem:**
`adk eval` only resolved `agent.root_agent`, while `AgentEvaluator` supports both `root_agent` and `get_agent_async`. This inconsistency caused valid agent modules (for `AgentEvaluator`) to fail in CLI evaluation.
**Solution:**
Aligned `adk eval` agent resolution with `AgentEvaluator` by updating CLI loading logic to support both entry points:
- `root_agent`
- `get_agent_async`
Implementation details:
- Made `get_root_agent` in `cli_eval.py` asynchronous and added fallback resolution to `get_agent_async`.
- Updated `cli_tools_click.py` to call `get_root_agent` via `asyncio.run(...)`.
- Added/updated unit tests for both resolution paths and the error path when neither is present.
### Testing Plan
**Unit Tests:**
- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.
```
% pytest tests/unittests/cli
========================= 271 passed, 140 warnings in 7.69s =========================
```
**Manual End-to-End (E2E) Tests:**
No manual E2E test was run for this PR.
### Checklist
- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [ ] I have manually tested my changes end-to-end.
- [x] Any dependent changes have been merged and published in downstream modules.
### Additional context
This PR focuses on one scoped item from #4410: agent resolution parity between `adk eval` and `AgentEvaluator` (`root_agent` / `get_agent_async`).
Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4413 from ftnext:adk-eval-get-agent-async 267c1964d55b9ddbad418999d5fd4e6ed94eb14e
PiperOrigin-RevId: 952361014
Two CLI builder-response tests wrote YAML fixtures with Path.write_text(), which on Windows translates newlines and changes the asserted payload. This writes the exact fixture bytes so the tests are stable across platforms; production behavior is unchanged and the endpoint still preserves file bytes.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 949184681