240 Commits

Author SHA1 Message Date
George Weale b0c599f21f fix(cli): report env var names instead of values when overriding env_vars
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970118568
2026-08-24 15:30:06 -07:00
George Weale a84a4b52aa fix(cli): clean up pytest subprocesses on test-client disconnect
Continuous Integration / Pre-commit Linter (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.10) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.11) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.12) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.10) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.11) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.12) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.14) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Waiting to run
Copybara PR Handler / close-imported-pr (push) Waiting to run
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
2026-08-24 15:10:43 -07:00
George Weale 00932e617e fix: only emit --gemini_enterprise_app_name for adk_version >= 2.2.0
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 969947489
2026-08-24 10:48:46 -07:00
Aarav Mittal e577c301d5 feat(cli): Support Cloud Build worker pools for Agent Engine deploy
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
2026-08-21 13:52:52 -07:00
ftnext dc735bd953 fix(cli): Preserve non-ASCII text in adk test --rebuild, Web UI test saving, and CLI JSONL
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
2026-08-20 10:43:26 -07:00
Google Team Member 3f2d399cd9 fix: resolve server disconnect logs
PiperOrigin-RevId: 967479906
2026-08-19 16:55:28 -07:00
Gaurav Gandhi 023f45c3e5 fix: resolve NameError in legacy create-eval-set route
Merge https://github.com/google/adk-python/pull/6681

PiperOrigin-RevId: 967357331
2026-08-19 12:49:41 -07:00
Fnu Abdullah 26381552c0 fix: disable Windows glob expansion for CLI args
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
2026-08-18 12:57:19 -07:00
chelsealong caac070837 fix: skip non-agent directories in AgentLoader.list_agents()
Merge https://github.com/google/adk-python/pull/6668

PiperOrigin-RevId: 966348937
2026-08-17 21:12:06 -07:00
chelsealong 1d2d1eda3c fix: point to Gemini Enterprise registration docs after agent_engine deploy
Merge https://github.com/google/adk-python/pull/6634

Fixes #6633

PiperOrigin-RevId: 964955729
2026-08-14 16:42:48 -07:00
Petr Marinec 3fa71b6349 fix: validate session initialization events
Merge https://github.com/google/adk-python/pull/5291

Prevent client-supplied session initialization events from seeding ADK runtime state, and tighten HITL confirmation resumption.

Fixes #5290

Co-authored-by: Jason Zhang <jasoncz@google.com>
PiperOrigin-RevId: 964799826
2026-08-14 11:29:12 -07:00
OiPunk c93fcc0930 fix: handle read-only .git files in deploy cleanup on Windows
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
2026-08-13 17:50:54 -07:00
Enkhbat.E 4f07c93b6e fix: report root_agent type mismatch instead of 'No root_agent found'
Merge https://github.com/google/adk-python/pull/6635

Fixes #6606

PiperOrigin-RevId: 964386706
2026-08-13 17:37:54 -07:00
Kathy Wu 2cf4fd1ddc fix: guard reads in the local API server against DNS rebinding
_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
2026-08-13 13:57:33 -07:00
chelsealong 374aab372a fix: add .adk/ to the .gitignore generated by adk create
Merge https://github.com/google/adk-python/pull/6649

Fixes #6647

PiperOrigin-RevId: 963678453
2026-08-12 15:00:36 -07:00
George Weale 899500510d fix: restrict builder YAML code references to the app being edited
Close #5292

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 963578247
2026-08-12 11:53:35 -07:00
Mukunda Rao Katta e03dbab2d4 fix(cli): normalize trigger user ids for sessions
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
2026-08-12 10:02:07 -07:00
Surajit Nandi aa9c187f46 fix(cli): stream_reasoning_engine raises StopIteration RuntimeError on sync generators
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
2026-08-11 10:54:24 -07:00
Kathy Wu 4ccc6be6d4 feat: add express mode telemetry logging for ADK CLI onboarding
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
2026-08-10 11:46:23 -07:00
Anas Khan f0b3ca601a fix: use _GCLOUD_CMD for gcloud calls in GKE deploy on Windows
Merge https://github.com/google/adk-python/pull/6297

PiperOrigin-RevId: 962249348
2026-08-10 10:57:11 -07:00
ftnext 0477e5743b feat(cli): auto-discover test_config.json for single eval file in adk eval
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
2026-08-09 16:37:31 -07:00
Yufeng He 568b4f6b54 fix: collect eval state from workflow nodes
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
2026-08-06 23:15:26 -07:00
abhiramArise 547f1ebaf4 fix: gate --sandbox-launcher behind gcloud beta run deploy
Merge https://github.com/google/adk-python/pull/6514

Closes #6511

PiperOrigin-RevId: 960588457
2026-08-06 17:20:45 -07:00
George Weale 456524d714 test: add unit tests for public symbols that had no coverage
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 960421043
2026-08-06 11:41:06 -07:00
Lucas Kang 0fcfe99a50 fix(cli): track full server duration and log routine Ctrl+C termination as success
- 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
2026-08-05 15:55:54 -07:00
George Weale c27d8688ed fix: scope file artifact reads and deletes to the requesting app
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 959831380
2026-08-05 13:16:30 -07:00
Liang Wu bb9465bc48 fix(test): tolerate both click exit codes when a group is invoked without a subcommand
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
2026-08-04 14:41:39 -07:00
Google Team Member 9630559830 fix(cli): update gcloud command to use beta flag
PiperOrigin-RevId: 958633981
2026-08-03 16:18:58 -07:00
Lucas Kang c12a025184 feat: capture TTY connectivity in CLI environment telemetry
- 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
2026-07-31 11:44:00 -07:00
Lucas Kang 77726c55b3 fix(cli): implement early telemetry recording for long-running web servers and log successful exit code upon routine teardown
- 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
2026-07-31 10:27:41 -07:00
doug 73ecb5b535 fix: wire App plugins through eval paths
Merge https://github.com/google/adk-python/pull/6480

Co-authored-by: Andrea Mestriner <andrea.mestriner@fractionaldata.io>
Fixes #5503

Co-authored-by: Yi Liu <yiliuly@google.com>
PiperOrigin-RevId: 956948990
2026-07-30 23:36:42 -07:00
George Weale d776f22c8e refactor: define StreamingMode in a leaf module the CLI can import
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 956760523
2026-07-30 15:15:03 -07:00
George Weale 8806dc2bd8 perf: improve adk import loading
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
2026-07-30 13:58:45 -07:00
Lucas Kang 2c6a7ffb4a feat: add parent terminal grouping and TTL pruning to ADK CLI telemetry
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
2026-07-29 18:07:22 -07:00
Lucas Kang a58220cd05 feat: add capability to log commands run in CLI
- 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
2026-07-29 12:10:46 -07:00
Lucas Kang 6bab08fc80 feat: add telemetry consent check, status commands, and interrupt safety to CLI
- 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
2026-07-28 15:48:21 -07:00
Lucas Kang 2280f1cc5b feat: add telemetry metrics collection for ADK CLI execution
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
2026-07-28 14:07:53 -07:00
George Weale 93db97db33 feat: add --extra_packages option to adk deploy agent_engine
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
2026-07-28 13:15:44 -07:00
Yifan Wang 8d2ded3bec feat: add agent identity auth manager finalize endpoint for 3 legged OAuth flow with auth manager
Co-authored-by: Yifan Wang <wanyif@google.com>
PiperOrigin-RevId: 955397715
2026-07-28 12:14:40 -07:00
George Weale 46aaa313f5 test: make unit contracts platform neutral
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
2026-07-27 14:37:57 -07:00
George Weale f72f0db58c fix(artifacts): namespace file artifacts by app
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
2026-07-27 13:05:18 -07:00
Stephen Allen 5091f0a65a feat(eval): Make live and audio evals reachable via public entrypoints
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
2026-07-27 11:08:51 -07:00
Bo Yang 40cf97bf2c fix(cli): treat agent folder with subfolders as single agent in adk web
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
2026-07-24 11:28:41 -07:00
Yifan Wang d33ca5fa7c fix: support nested agent paths in dot_adk_folder resolution
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
2026-07-23 12:49:20 -07:00
Lucas Kang 26f3d454c7 feat: Add telemetry consent configuration endpoints and local writing utility
- 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
2026-07-23 11:41:01 -07:00
Jae 67ab27f254 fix: allow invocation-level rubrics
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
2026-07-22 18:14:34 -07:00
George Weale 6e9895c55c fix: preserve ADK behavior on Windows
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
2026-07-22 17:14:21 -07:00
nikkie ebaef9f632 fix(eval): support get_agent_async in adk eval
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
2026-07-22 15:21:21 -07:00
Shikhar Goel 6f6106f672 fix: handle Windows paths in adk eval
Merge https://github.com/google/adk-python/pull/6419

Fixes #6415

PiperOrigin-RevId: 951036899
2026-07-20 13:56:09 -07:00
George Weale 4a84d8a459 test: preserve YAML fixture newlines
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
2026-07-16 14:45:01 -07:00