2978 Commits

Author SHA1 Message Date
Shangjie Chen 99179223d0 fix: Guard invocation_context.branch assignment against mock SessionService instances
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
When running with a mocked `SessionService` whose `append_event` returns a `Mock`
object, `user_event.branch` dynamically evaluates to an `AsyncMock`/`MagicMock`
instead of `None` or a `str`. Guarding with `isinstance(..., str)` ensures
`invocation_context.branch` is only updated when a real string branch is
present, preventing downstream Pydantic validation and Proto conversion errors.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 970212560
2026-08-24 18:30:15 -07:00
Shangjie Chen 646c97645c refactor: move the branch and node-path helpers out of Runner into the
modules that own those concepts

No behavior change. `Runner` had grown four private helpers that parse node
paths, classify branches and scan events, none of which depend on runner state:

- `_strip_run_ids_from_path` -> `_NodePathBuilder.static_path`. Node paths are
  `/`-separated and owned by `_NodePathBuilder`, which already strips the run id
  off the leaf segment; this generalises that to every segment instead of
  reimplementing the parsing in `Runner`.
- `_is_tool_branch` -> `_BranchPath.is_tool_branch`. Branches are
  `.`-separated and owned by `_BranchPath`, which the helper already depended on.
- `_collect_function_call_ids` -> `_collect_function_call_ids` in
  `flows/llm_flows/functions`, next to `find_event_by_function_call_id`.
- `_find_static_node_path` -> `find_static_node_path` in `workflow/_base_node`,
  the module that defines the node tree it walks.

Keeping the two path types with their own classes also removes a hazard: node
paths and branches both look like `name@id` segments but use different
separators, so parsing them by hand in a third module invites applying the wrong
one.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 970192989
2026-08-24 17:45:41 -07:00
George Weale e649a382aa docs: correct node schema docstrings about which variants validate
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970146748
2026-08-24 16:17:18 -07:00
George Weale 186d9f739d fix: send the required list on a CrewAI or LangChain function declaration
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970145628
2026-08-24 16:15:34 -07:00
George Weale f604de2b96 perf: buffer streamed function call arguments instead of concatenating
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970134171
2026-08-24 15:56:59 -07:00
George Weale 61d904345a refactor(types): narrow the annotations in the application integration and API hub toolsets
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970133587
2026-08-24 15:55:08 -07:00
George Weale e68fab7b37 refactor(types): type the auth credential and manager surfaces for strict mypy
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970130539
2026-08-24 15:50:56 -07:00
George Weale 0de4cc2371 refactor(types): type the environment simulation tools for strict mypy
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970127626
2026-08-24 15:46:32 -07:00
George Weale 96573404a5 refactor(types): type the built-in plugins for strict mypy
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970126718
2026-08-24 15:44:41 -07:00
George Weale fb15710548 fix(sessions): load legacy v0 event actions with the restricted unpickler
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970123496
2026-08-24 15:39:06 -07:00
George Weale e4601a1636 refactor(types): type the Bigtable and Pub/Sub tools and the GoogleTool base for strict mypy
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970121742
2026-08-24 15:37:06 -07:00
George Weale 8499fd8505 refactor(types): type the computer use, data agent, retrieval and Google API tools for strict mypy
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970121611
2026-08-24 15:35:54 -07:00
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 0321136775 fix(telemetry): record call_llm span attributes when a plugin short-circuits the model call
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970099275
2026-08-24 14:57:06 -07:00
George Weale b1c984baa2 fix: honor ContextCacheConfig on the LiteLLM path
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970091291
2026-08-24 14:44:11 -07:00
Shangjie Chen 676d1e7645 fix: Restore invocation_context.branch from session history when executing a non-root agent node or resuming a sub-agent in Runner
Any event the node authored is eligible, including a plain text turn, which is
all a non-resumable sub-agent may leave behind. Tool messages are excluded: a
tool's user-facing message is authored under the agent's name and carries the
agent's node path, but is published on `<tool>@<function_call_id>`, so a branch
whose trailing id matches a function call in the session is skipped. Nodes are
matched by static node path (run ids stripped), falling back to author/name for
path-less legacy events, disambiguating sub-agents that share a name. At the
resume sites the scan is scoped to the invocation being resumed; the
new-invocation and live paths use the most recent matching event.

This supersedes the earlier (unsubmitted) cl/944151553, whose path-matching and
invocation-scoping approach is folded in here.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 970044047
2026-08-24 13:31:27 -07:00
George Weale 811d379fdf fix: emit Anthropic prompt cache breakpoints for ContextCacheConfig
Fixes #5395

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970042984
2026-08-24 13:29:39 -07:00
George Weale 69a57099c8 refactor(types): narrow the annotations in the OpenAPI tool spec parser
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970030668
2026-08-24 13:09:48 -07:00
George Weale ef158d32e9 fix(a2a): keep file payloads out of the debug log line
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970016493
2026-08-24 12:44:53 -07:00
George Weale ece924c2dd fix: deliver parallel sub-agent failures to the caller
Close #5455

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970014840
2026-08-24 12:41:52 -07:00
Yifan Wang d1ed104d2a chore: add telemetry to browser runtime configuration
This adds a default "telemetry" key initialized to null in runtime-config.json

Co-authored-by: Yifan Wang <wanyif@google.com>
PiperOrigin-RevId: 970012184
2026-08-24 12:36:37 -07:00
George Weale d8df6fd1fe fix(workflow): honor START as a satisfied JoinNode predecessor
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 970004551
2026-08-24 12:24:23 -07:00
George Weale 816ada5318 fix(workflow): do not act on function calls in partial events
Close #6583

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 969986396
2026-08-24 11:52:16 -07:00
George Weale 203461cdc4 perf: resolve the optional id-pairing providers once, not per request
Content assembly decided whether to preserve adk- function call ids by
resolving AnthropicLlm, LiteLlm and OpenAIResponsesLlm through inline
try/except imports on every LLM request. Python does not cache a failed
import, so an install without those optional packages re-ran the module
finder and re-executed the shim module bodies each time. The lookup now
lives in a memoized module-private helper.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 969983972
2026-08-24 11:47:49 -07:00
Kathy Wu 4357fdc141 refactor: declare ADK's own http-client-factory protocol
`CheckableMcpHttpClientFactory` exists to add `@runtime_checkable` to the SDK's
`McpHttpClientFactory`. Pydantic compiles a Protocol-annotated field into an
`is-instance` validator, and that fails at class construction time on a
protocol without it, so `SseConnectionParams` and
`StreamableHTTPConnectionParams` cannot declare `httpx_client_factory` any
other way.

The base class it inherits is not public. It lives in
`mcp.shared._httpx_utils`, is absent from that module's `__all__`, and reaches
ADK only because `mcp.client.streamable_http` happens to re-export it. A
release that stops re-exporting it makes this module fail to import, and with
it every MCP tool.

Declare the protocol here instead. Structural typing means a factory written
against either declaration satisfies both, so nothing else changes. The
signature still has to match the SDK's: `_DebugHttpxClientFactory` wraps the
given factory and calls it by keyword, and `sse_client` receives that wrapper,
typed there with the SDK's own protocol.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 969961072
2026-08-24 11:11:11 -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
chelsealong c6020659a0 fix: keep session identity across single-turn node contexts
Merge https://github.com/google/adk-python/pull/6697

Fixes #6691

PiperOrigin-RevId: 969938117
2026-08-24 10:33:41 -07:00
Google Team Member dc00db51bc feat(telemetry): add per-invocation token spend metrics for invoke_agent
Adds six histograms for one agent invocation's token spend, alongside the
existing `gen_ai.invoke_agent.{inference,tool}_calls`:

  adk.experimental.invoke_agent.input_tokens
  adk.experimental.invoke_agent.output_tokens
  adk.experimental.invoke_agent.total_tokens
  adk.experimental.invoke_agent.cache_read.input_tokens
  adk.experimental.invoke_agent.reasoning.output_tokens
  adk.experimental.invoke_agent.tool.input_tokens

Gated, as `adk.experimental.*` metrics are, on the experimental-telemetry
opt-in: `RunConfig.telemetry.adk_experimental_telemetry_opt_in`, falling back
to `ADK_EXPERIMENTAL_TELEMETRY`.
PiperOrigin-RevId: 969833352
2026-08-24 07:13:49 -07:00
Shangjie Chen e753651b7d fix: prevent duplicate synthetic user event on single-turn agent resumption
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
When resuming a single-turn agent node from an HITL tool confirmation pause,
prepare_llm_agent_input was unconditionally appending a synthetic user event,
shadowing the user's FunctionResponse and causing an infinite confirmation loop.
This change skips synthetic user input injection when resume_inputs are present.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 969178203
2026-08-22 17:44:30 -07:00
Kathy Wu d9f4d3d288 fix: stop the session liveness probe from crashing when the SDK moves its streams
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
`_is_session_disconnected` reads `session._read_stream._closed` and
`session._write_stream._closed`. Four attribute reads, all four private to the
MCP SDK, none of them promised.

The probe is not the only liveness signal. `create_session` pairs it with
`SessionContext._is_task_alive`, which ADK owns and which catches strictly
more: a crashed transport can leave both streams open while the task behind
them is already dead. So a missing attribute has a sensible answer -- treat
the session as connected and let the task check decide -- and no reason to
take down the call with an `AttributeError`.

Read the four defensively and say in the docstring where liveness actually
comes from. No behaviour change while the SDK keeps the streams: a closed
stream still reports disconnected, and either stream counts.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968726270
2026-08-21 15:33:58 -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
George Weale e3ae4ac2b4 fix(sessions): stop dropping event actions on v0 PostgreSQL migration
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 968671770
2026-08-21 13:51:31 -07:00
George Weale c0614d6580 feat: record context cache state on the LLM call span
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 968671743
2026-08-21 13:50:38 -07:00
jun weijia 26110c7559 fix: reject negative recent-event limits
Merge https://github.com/google/adk-python/pull/6687

Unsupported negative limits should fail at configuration time, before a session backend handles them.

PiperOrigin-RevId: 968610704
2026-08-21 11:46:22 -07:00
Kathy Wu d18df2fa1c fix: detect MCP tool errors under either field spelling
`_detect_error_in_response` reads `isError` off the dumped `CallToolResult`.
MCP SDK 1.x names that field `isError`, so the lookup works. 2.x renames it to
`is_error`, so `model_dump` emits the snake_case key and the lookup returns
`None` for every result. Tool errors would stop being reported to telemetry,
with nothing in the logs to say so.

`_get_declaration` has the same problem one step earlier: it reads
`inputSchema` and `outputSchema`, which 2.x renames and removes. That one at
least fails loudly with an `AttributeError`.

Read both spellings. `_read_field` returns the first attribute a model
defines, so it picks up the camelCase name on 1.x and the snake_case name on
2.x, and raises a named error if a future release renames the field again.

`model_dump(by_alias=True)` would have been a smaller change, but it is not
equivalent: every MCP result model aliases `meta` to `_meta`, so dumping by
alias would rename that key in the payload ADK returns to the caller. The
casing of the returned dict is left alone here and belongs with the SDK
upgrade itself.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968586089
2026-08-21 10:59:07 -07:00
Kathy Wu 574da43d65 refactor: build the MCP read timeout in one place
`SessionContext._start` builds `ClientSession`'s `read_timeout_seconds` twice,
once for stdio and once for SSE and streamable HTTP, each with its own inline
`timedelta(...) if ... is not None else None`.

Move that to `_read_timeout`. ADK carries every timeout as float seconds and
now converts once, at the boundary where the SDK is called.

This is a plain de-duplication today. It also isolates a difference between
MCP SDK versions: 1.x types `read_timeout_seconds` as a `timedelta` and 2.x
types it as a float, so the conversion is the only line that has to change.

The explicit `is None` check is deliberate. A zero timeout is a real value, not
a missing one, and a truthiness check would silently turn it into "no timeout".

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968580764
2026-08-21 10:49:05 -07:00
Liang Wu 98896eb2aa feat(live): let a live streaming tool send messages to the user directly
A streaming tool used to only talk to the model: every value it yielded came
back as a FunctionResponse. Therefore it costs model context and could derail the model's reasoning.

An Event with message yielded by a streaming tool is now addressed to the user instead. It is enqueued on the invocation's event queue, so the runner appends it to the session and streams it to the client, and it is never sent over the live model
connection. Plain values are still sent to the model as FunctionResponse. A tool can mix and match any number of each, in any order.

Runner's run_live also now initializes the invocation's event queue and merges it with the live agent's own event stream. Without a queue, anything running under
the live agent that enqueues an event -- a streaming tool, or a node -- fails
with "_event_queue is not set".

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 968533249
2026-08-21 09:15:04 -07:00
chelsealong 775c1bd36e feat: support opt-in session retention in MultimodalToolResultsPlugin
Merge https://github.com/google/adk-python/pull/6698

Adds a `retention` parameter to `MultimodalToolResultsPlugin` to allow
tool-returned parts to persist across turns in a session.

Fixes #6695

PiperOrigin-RevId: 968187977
2026-08-20 18:57:27 -07:00
Google Team Member 77cfe4349d refactor: simplify default max LLM calls configuration
PiperOrigin-RevId: 968166903
2026-08-20 18:03:21 -07:00
Kathy Wu 3c977bc2ef fix(skills): skip search results that fail frontmatter validation
`GCPSkillRegistry.search_skills` built a `Frontmatter` for every hit with no
per-item error handling. `Frontmatter.name` must be kebab-case (or snake_case
behind the feature flag), but the catalog holds names outside that set -- the
first-party entry `cloud.google.com-agent-platform-eval-flywheel` has dots. The
first such hit raised a pydantic `ValidationError` and took down the whole
call, so search returned nothing at all against any catalog that holds one
non-conforming entry. An entry with an empty description did the same.

Skip the entry and log a warning instead. The caller does not control what the
catalog holds, so one entry it never asked about must not break discovery for
everything else.

A name that is not a string gets the same treatment. `.split` on it raises
before validation is reached, which would sink the call the same way.

Fixes #6838

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968161389
2026-08-20 17:49:58 -07:00
garyzava dac18699b9 fix: honor before_run_callback early-exit for Workflow runs
Merge https://github.com/google/adk-python/pull/6032

In the Workflow run path, capture the before_run_callback result and early exit if it is Content, matching the non-workflow path behavior.

Fixes #6013

PiperOrigin-RevId: 968159218
2026-08-20 17:45:20 -07:00
brucearctor a01d516a6b feat: support injecting custom LLM clients into Gemini and AnthropicLlm
Merge https://github.com/google/adk-python/pull/5035

Enable injecting pre-configured LLM clients into Gemini and AnthropicLlm models to support multi-agent systems with distinct configurations.

Fixes #5027

PiperOrigin-RevId: 968151995
2026-08-20 17:29:45 -07:00
Google Team Member 75679db3fa feat: add ADK_MAX_LLM_CALLS environment variable to configure max LLM calls limit
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
PiperOrigin-RevId: 968115816
2026-08-20 16:14:11 -07:00
Xuan Yang 9a32eba1e2 fix: revert the A2A guard that broke every HITL tool confirmation
Reverts #6462. An agent served over A2A could no longer satisfy a
human-in-the-loop tool confirmation at all: the request converter stamped
`a2a_metadata` on every inbound invocation unconditionally, and the
confirmation processor returned early whenever that key was present, dropping
the operator's legitimate approval along with a forged one. Where A2A is the
only channel reaching the human operator, every tool requiring confirmation was
permanently blocked.

The guard also did not close the vector it targeted: #6461 notes `/run` and
`/run_sse` are unauthenticated by default, so the same forged approval still
lands over plain HTTP. Gating on the transport rather than on the identity of
the approving principal is at once too broad and too narrow.

Reopens #6461.

Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 968106938
2026-08-20 15:58:14 -07:00
Shangjie Chen a7bf3d7f49 chore: Consolidate other agent presentation and fencing helpers into _fencing.py
Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 968098131
2026-08-20 15:42:00 -07:00
Kathy Wu d6954946df feat: resolve auth for RemoteA2aAgent in AgentRegistry
`AgentRegistry.get_mcp_toolset` already derives an `auth_scheme`/`auth_credential`
pair from a registered resource's auth provider binding, but
`get_remote_a2a_agent` ignored bindings entirely. A caller who registered an A2A
agent behind an auth provider had no way to reach it: the returned
`RemoteA2aAgent` sent unauthenticated requests, and the caller could not supply
a credential either.

Accept `auth_scheme`, `auth_credential` and `continue_uri` on
`get_remote_a2a_agent` and forward the pair to `RemoteA2aAgent`. When no scheme
is passed, resolve the agent's `authProviderBinding` into a
`GcpAuthProviderScheme`. The signature and the resolution path mirror
`get_mcp_toolset`, so both registered resource types authenticate the same way.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968076388
2026-08-20 15:04:17 -07:00
Google Team Member 393ec0858b feat: add location parameter to list_accessible_data_agents in data_agent toolset
Allow callers to explicitly specify the Google Cloud location when listing data agents, following standard three-level precedence (explicit argument, toolset config location, falling back to global).

PiperOrigin-RevId: 968067492
2026-08-20 14:48:51 -07:00
Kathy Wu d42c634bd6 feat: support auth_scheme and auth_credential in RemoteA2aAgent
`RemoteA2aAgent` could not authenticate its calls; a caller had to bake a static
token into a custom `httpx_client`. Every other ADK component with a remote
endpoint takes an `auth_scheme`/`auth_credential` pair.

Accept that pair and an optional `credential_key`. `CredentialManager` resolves
the credential once per invocation, and the headers go on the card fetch and the
message send; with nothing to send, the agent emits `adk_request_credential`.
The interceptors and the derived key are per agent, so one agent's token cannot
reach another agent's host.

`build_auth_headers` also stops sending `Bearer None` for a tokenless OAuth2
credential. The interactive round trip needs an `LlmAgent` parent; the
`AgentRegistry` path resolves server-side.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968025847
2026-08-20 13:38:19 -07:00
Shangjie Chen af653512ce refactor(llmflows): simplify contents processor by extracting compaction
**Problem:**
contents.py was over 1,400 lines and mixed high-level LLM request content assembly with low-level event compaction filtering and missing function-call recovery logic.

**Solution:**
Extract _process_compaction_events and _recover_compacted_function_calls into a private helper module _content_compaction.py, preserving re-exports in contents.py for backward compatibility, and move corresponding unit tests into test__content_compaction.py.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 967989388
2026-08-20 12:38:13 -07:00