StreamingResponseAggregator accumulated streamed text with `+=` on instance
attributes; the live audio cache combined chunks with `bytes +=`. Use
list/join and b''.join instead.
`+=` on an attribute compiles to STORE_ATTR, so it does not get CPython's
in-place concat optimization that makes the same statement on a local variable
cheap. Every chunk therefore re-copies the whole buffer, making aggregation
O(n^2) in the length of the response. It is invisible on short replies and
only bites long streamed answers and long live-audio captures, which is why it
has not surfaced as a bug report.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 956076793
When an OpenAPI OAuth2 credential has an expired access token and a
refresh token, refresh it (reusing the shared OAuth2 helpers) before
wrapping it as a bearer token, instead of returning the stale token.
Refresh failures fall back to the existing token.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 956058160
- 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
Fixes a crash in the A2A converter pipeline caused by type validation failures when handling `custom_metadata`.
1. **Serialization Fix**: Updates legacy event serialization to use standard `json.dumps` for plain dictionaries and lists. Previously, these relied on standard stringification (`str()`), which produces single-quoted (invalid JSON) representations in Python, causing downstream JSON parsing failures.
2. **Parser Hardening**: Enforces strict type checking during inbound metadata extraction. Explicitly validates that `custom_metadata` decomposes to a `dict` before passing it to downstream models. Raw string fallbacks (from failed JSON decodes) are now properly discarded instead of causing Pydantic validation errors.
Includes regression tests for both collection serialization and type-safe metadata fallback in the A2A conversion pipeline.
PiperOrigin-RevId: 956003782
Wire the existing to_google_genai_finish_reason mapping into both the
non-streaming and streaming responses; previously finish_reason was
always unset on Claude responses.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955985680
The GAPIC retrieve calls only raise GoogleAPIError / GoogleAuthError, so
catch those (plus TimeoutError on the polling paths) and wrap them with
context as before. Other exceptions now propagate instead of being masked
as credential failures, surfacing real bugs.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955931557
On the Vertex AI Agent Runtime CPU is throttled the instant a request
finishes, so a background periodic metric exporter is starved between
requests and drops data. This adds a request-driven metric
reader (and the span processor + middleware that drive it) that
collects and exports metrics on the request path, where CPU is
guaranteed, without adding latency to any request.
The default GCP metric exporter is also switched to a raw OTLP push exporter
over telemetry.googleapis.com.
Co-authored-by: Max Ind <maxind@google.com>
PiperOrigin-RevId: 955921308
- 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
FunctionTool._get_declaration ran pydantic create_model + JSON-schema
generation for every tool on every LLM step, even though the result is fixed
once the tool is constructed. Memoize the build keyed by (func, ignored
params, API variant, feature flag) and return a copy so callers (e.g. toolset
prefixing) can still mutate the result.
The cache is module-level rather than per-instance because a bare callable in
LlmAgent.tools is re-wrapped into a fresh FunctionTool on every step, which
defeats an instance cache for exactly the callers this costs the most.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955481497
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
Adds OCIGenAILlm under integrations/oci/, for Google Gemini and other models
hosted on Oracle Cloud Infrastructure Generative AI. Optional install:
pip install google-adk[oci]. LLMRegistry auto-routing and the
google.adk.models import surface are preserved.
The OpenAI-compatible transport from the source PR (OCIGenAIOpenAILlm) is
not taken. It reimplemented the message, tool and response conversion plus
the streaming loop that OpenAILlm already provides; the right form is a
small subclass overriding the OpenAI client, which cannot live in
integrations/ while OpenAILlm is still experimental. It can land separately
once that settles.
The OCI client is now built once per instance rather than per request, so a
call no longer re-reads the OCI config from disk.
Merge https://github.com/google/adk-python/pull/5285Closes#5069
Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5285 from fede-kamel:feat/oci-generative-ai 0230acc0a93b7e43014f2ef3a8b89de463a50bd8
PiperOrigin-RevId: 955453382
The new guide explains what the plugin does, a get-started
example, how the retry loop and per-tool failure tracking
work, the configuration options, advanced applications,
and its limitations.
Co-authored-by: Jason Zhang <jasoncz@google.com>
PiperOrigin-RevId: 955444373
Tool thread pools lived in a process-global registry keyed by max_workers and
were never shut down, so every distinct worker count ever requested left a pool
of idle threads alive for the life of the process. The registry is now keyed
weakly by the event loop a pool serves, and each pool is shut down once that
loop is collected. Executors are not bound to a loop, so the defect here is
leaked idle threads rather than threads doing work on a loop they do not belong
to.
Tool calls keep their own pool rather than moving to the loop's default
executor. The loop uses that executor for its own work, including name
resolution, so sharing it would let a blocking tool starve the loop and would
also drop the adk_tool_executor thread name.
max_workers keeps its meaning as the size of that pool, so invocations sharing
a loop share its threads. Because a pool now belongs to one loop rather than to
the process, a program driving several loops at once can hold max_workers tool
threads per loop where it previously held that many in total. Tool execution is
otherwise untouched: cancelling a call still abandons a thread that has already
started, and still drops a call that has not started.
This path only runs when tool_thread_pool_config is set, so the default
configuration is unaffected. There are no public API changes; the pool accessor
is module private.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955439650
VertexAiRagMemoryService.add_session_to_memory and search_memory are both
declared async but called the synchronous RAG SDK, so every upload and
retrieval blocked the event loop for the duration of the HTTP round trip.
Both now use the SDK async surface (Client(...).aio) and await the calls.
Each operation owns one async client and closes it in a finally, so the
underlying HTTP session is released on success, failure and cancellation
alike. The close is shielded: a single cancellation is survivable without a
shield, but a second one arriving while the close is suspended - an enclosing
deadline expiring while an inner one is already unwinding, for example -
would otherwise interrupt the close itself and leak the HTTP session.
The session transcript is written to a plaintext temporary file before
upload. Previously that file was removed only on the success path, so a
failed or cancelled upload left the transcript on disk indefinitely. Removal
now happens in a finally, and the path is recorded before the write so a
failed write is cleaned up too. Corpus names are validated before the file is
written, and the file is opened with an explicit utf-8 encoding instead of
the locale default.
Multi-corpus uploads stay sequential and fail fast, because the RAG API
offers no cross-corpus transaction or rollback. The per-corpus RAG handle is
fetched once instead of rebuilt on every iteration.
Behavior change: add_session_to_memory now raises ValueError when a
configured RAG resource has no rag_corpus, which is reachable by constructing
VertexAiRagMemoryService() with no arguments. That configuration previously
wrote the transcript to disk and then failed inside the SDK with
corpus_name=None, so this replaces a late, opaque failure with an early one.
The message changed from "Rag resources must be set." to "rag_corpus must be
set on every RAG resource.", which describes the condition actually checked.
The CLI factory rejects an empty corpus and always passes a fully-qualified
name, so it is unaffected.
The async and sync RAG upload surfaces shipped in the same SDK release, so
this does not raise the minimum google-cloud-aiplatform version.
Scope note: this removes the blocking SDK calls, but the client is still
constructed synchronously, and when neither an explicit project nor a
fully-qualified corpus name resolves a project id, that constructor loads
application default credentials on the event loop. Token refresh on the
request path is already offloaded to a thread by the SDK. These methods are
therefore not yet fully non-blocking.
Public method signatures are unchanged and no public symbol is added.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955437238
Merge https://github.com/google/adk-python/pull/5924
END_PUBLIC
## Summary
Fixes two issues that prevent Claude-family models from entering the ReAct tool-calling loop when used via LiteLLM inside a nested `AgentTool`:
1. **`AgentTool` with `input_schema`** — the serialized JSON payload sent as the first message causes Claude to interpret the request as already complete and respond directly without calling any tools.
2. **`tool_choice` not propagated** — `llm_request.config.tool_config.function_calling_config.mode` was not forwarded to LiteLLM's `completion_args`, so callers could not enforce tool use at the request level.
## Changes
### `src/google/adk/tools/agent_tool.py`
Wrap the serialized `input_schema` JSON in a natural-language instruction that explicitly asks the inner agent to use its available tools before producing a response. This keeps Claude in ReAct mode regardless of the message content format.
### `src/google/adk/models/lite_llm.py`
Read `llm_request.config.tool_config.function_calling_config.mode` and map it to LiteLLM's `tool_choice` parameter:
- `ANY` → `"required"`
- `NONE` → `"none"`
- `AUTO` → provider default (unchanged, key omitted from `completion_args`)
`_get_completion_inputs` now returns a 5-tuple `(messages, tools, response_format, generation_params, tool_choice)`.
## Unit Tests Added
### `tests/unittests/tools/test_agent_tool.py`
- `test_run_async_no_input_schema_passes_request_unchanged`: without `input_schema`, the content passed to the inner runner is `args['request']` verbatim.
- `test_run_async_with_input_schema_wraps_in_natural_language`: with `input_schema`, the text begins with `"Process the following structured request"`, contains `"Request:\n"` followed by the JSON payload, and is not a bare JSON blob.
- `test_run_async_with_input_schema_text_not_raw_json`: asserts the text does not start with `{`.
### `tests/unittests/models/test_litellm.py`
- `test_get_completion_inputs_tool_choice_none_without_tool_config`: `tool_choice` is `None` with no `tool_config`.
- `test_get_completion_inputs_tool_choice_required_for_any_mode`: returns `"required"` for `ANY` mode.
- `test_get_completion_inputs_tool_choice_none_for_none_mode`: returns `"none"` for `NONE` mode.
- `test_get_completion_inputs_tool_choice_none_for_auto_mode`: returns `None` for `AUTO` mode.
- `test_generate_content_async_propagates_tool_choice_required`: `acompletion` receives `tool_choice="required"` for `ANY`.
- `test_generate_content_async_propagates_tool_choice_none_mode`: `acompletion` receives `tool_choice="none"` for `NONE`.
- `test_generate_content_async_omits_tool_choice_for_auto_mode`: `tool_choice` key absent from `completion_args` for `AUTO`.
- `test_generate_content_async_omits_tool_choice_without_tool_config`: `tool_choice` key absent when no `tool_config`.
Also updated all existing `_get_completion_inputs` call sites (10 occurrences) to unpack the new 5-tuple.
## Pytest Results
```
tests/unittests/tools/test_agent_tool.py + tests/unittests/models/test_litellm.py
1 failed (pre-existing: test_custom_schema[GOOGLE_AI] — unrelated to this PR),
302 passed, 1 skipped in 2.98s
New tests: 11 passed (3 agent_tool + 8 litellm)
```
The 1 pre-existing failure (`test_custom_schema[GOOGLE_AI]`) is a `pydantic.ValidationError` that reproduces on the base branch before any of these changes and is unrelated to this fix.
## Related
- Fixes#5926
- Addresses #773 (expose tool_choice to callers via FunctionCallingConfig)
- Related #1063 (fixed FunctionDeclaration description in v1.20, different issue)
Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5924 from ecanlar:fix/agent-tool-input-schema-tool-choice-litellm 9cd8f31167e0a2a55c76e6cc8ea4a7d0850fd243
PiperOrigin-RevId: 955433983
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
Merge https://github.com/google/adk-python/pull/5852
## Link to Issue or Description of Change
### Problem
When a `RemoteA2aAgent` is used as a static node in a `Workflow` graph that feeds into a `JoinNode`, the joined output contains `None` for every `RemoteA2aAgent` predecessor.
Reproducer (simplified from a real coordinator graph):
```python
parallel_investigation_join = JoinNode(name="parallel_investigation_join")
Workflow(
edges=[
("START", account_context_agent, parallel_investigation_join), # RemoteA2aAgent
("START", ticket_history_agent, parallel_investigation_join), # RemoteA2aAgent
("START", diagnostics_agent, parallel_investigation_join), # RemoteA2aAgent
...
]
)
```
Observed `JoinNode` input:
```yaml
parallel_investigation_join:
account_context_agent: null
ticket_history_agent: null
diagnostics_agent: null
```
Root cause: `RemoteA2aAgent` inherits the default `BaseAgent._run_impl`, which iterates `run_async` and yields events without ever setting `event.output` or `event.node_info.message_as_output`. As a result, `NodeRunner._track_event_in_context` leaves `ctx.output` as `None`, and `Workflow._handle_completion` never records an entry in `loop_state.node_outputs` for that predecessor. `JoinNode` then sees `None` for it.
`LlmAgent` already solves the equivalent problem by overriding `_run_impl` and promoting the model's text reply to `event.output` (via `process_llm_agent_output` in `_llm_agent_wrapper.py`). `RemoteA2aAgent` had no equivalent hook.
### Solution
Add a workflow-only override of `_run_impl` on `RemoteA2aAgent` that mirrors `LlmAgent`'s behavior. For each event yielded by `BaseAgent._run_impl`, a new `_promote_response_to_output` helper joins the text of all parts that are **not** thoughts, function calls, or function responses, assigns it to `event.output`, and sets `event.node_info.message_as_output = True` (consistent with `LlmAgent`, prevents `NodeRunner._flush_output_and_deltas` from emitting a duplicate trailing output event).
The helper skips:
- partial events (streaming chunks)
- events not authored by this agent
- events whose `event.output` is already set
- events whose content carries only thoughts (streaming `working` / `submitted` task statuses that the legacy `_handle_a2a_response` marks `thought=True`)
- events whose content carries only function calls (the `input_required` / `auth_required` mock function call inserted by `_create_mock_function_call_for_required_user_input` — those should remain interrupts, not outputs)
- events whose A2A task state is non-final (`submitted`, `working`, `input-required`, `auth-required`, `unknown`). The v2 integration path (`_handle_a2a_response_v2`) delegates to converters that do **not** mark streaming `working` text as `thought=True`, so the thought filter alone is not enough. Without this guard, a `working` text event and the subsequent `completed` text event would each try to set `event.output`, causing `NodeRunner` to raise `ValueError: Output already set` on the second event and aborting the run before the real final answer ever surfaced. The state is read from `event.custom_metadata['a2a:response']['status']['state']`, which `_run_async_impl` already stamps before yield. Plain `A2AMessage` responses (no status field) and terminal task states (`completed`, `failed`, `canceled`, `rejected`) still promote.
In addition, `_run_impl` short-circuits after the first successful promotion. This protects against the case where a server emits multiple terminal-state events for one run (e.g. a `completed` status update followed by trailing artifact updates on the same already-completed task) — only the first terminal event becomes the node's output, subsequent ones pass through untouched.
Scope is intentionally narrow: only the agent boundary is touched. `to_adk_event.py` and the workflow scheduler are unchanged, since the same workaround (promoting content → output at the agent layer) is what `LlmAgent` does and what keeps the fix local.
## Testing Plan
### Unit Tests
- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.
Added `TestRemoteA2aAgentWorkflowOutput` in `tests/unittests/agents/test_remote_a2a_agent.py` (20 cases).
### Manual End-to-End (E2E) Tests
I ran the failing workflow described in the **Problem** section against [a real ADK app](https://github.com/gdsc-osaka/customer-support-agent-example/blob/2b2882ed0bd0e918aeaaa38b2e420f129db47dc1/agents/coordinator/agent.py#L27): a `Workflow` graph whose `START` fans out into multiple `RemoteA2aAgent` nodes that all feed into a single `JoinNode`. Each remote specialist runs as its own A2A server; the coordinator runs the workflow and forwards the joined dict to a downstream synthesis step.
Before the fix:
<img width="1624" height="1060" alt="Screenshot 2026-05-26 at 15 32 06" src="https://github.com/user-attachments/assets/22ddb04b-4c1d-4c75-a2ec-d4f1e87bc825" />
After the fix:
<img width="1580" height="1016" alt="Screenshot 2026-05-26 at 15 30 25" src="https://github.com/user-attachments/assets/0848a663-504f-4e60-99ff-81aa70daf164" />
### 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.
- [x] I have manually tested my changes end-to-end.
- [x] Any dependent changes have been merged and published in downstream modules.
### Additional context
The fix mirrors the pattern `LlmAgent` already uses (`process_llm_agent_output` in `src/google/adk/workflow/_llm_agent_wrapper.py`), keeping output-promotion at the agent boundary rather than touching the workflow scheduler or the A2A converters. This minimizes blast radius and avoids regressions in non-workflow usages of `RemoteA2aAgent`, where the `_run_impl` path is not exercised.
Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5852 from Harineko0:fix/remote-a2a-agent-workflow-output 0f262921e5ccbe24fbb33427dd378fad857cd8da
PiperOrigin-RevId: 955426327
Artifacts are keyed by (app_name, user_id, session_id), but AgentEvaluator
gave callers no way to supply an artifact service and always generated a
random session id per eval case, so pre-loaded artifacts were unreachable
during eval. Thread an optional artifact_service through
AgentEvaluator.evaluate and evaluate_eval_set into LocalEvalService, and
add an optional SessionInput.session_id that is honored per eval case so
each case can target the session its artifacts live under. Both additions
default to preserving today's behavior.
A pinned session id is reused rather than replaced: when a session already
exists under that id the eval runs against it, so a session the caller
prepared keeps its events and state. Three consequences of reusing:
- SessionInput.state is applied only when the session has to be created.
Pinning an existing session and also setting state does not merge that
state into the existing session.
- With num_runs > 1 every run of a pinned case shares one session, so
events accumulate and later runs see earlier runs' history. Cases that
do not pin an id are unaffected and still get a fresh session per run.
- Two eval cases pinned to the same id share that session and its
artifacts, and append to it concurrently when cases run in parallel. A
pinned id should be unique per eval case.
Close#2075
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955381704
Merge https://github.com/google/adk-python/pull/5942
**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
**2. Or, if no issue exists, describe the change:**
**Problem:**
`ToolConnectionAnalyzer.analyze()` in
`src/google/adk/tools/environment_simulation/tool_connection_analyzer.py`
crashes whenever the analyzed LLM returns a response that is not valid JSON.
The JSON parse is wrapped in a `try/except`, but the `except` clause does not
bind the exception:
```python
except json.JSONDecodeError:
logging.warning(
"Failed to parse tool connection analysis from LLM. Proceeding"
" without connection map. Error: %s\nLLM Output:\n%s",
e, # <-- 'e' was never defined
response_text,
)
return ToolConnectionMap(stateful_parameters=[])
```
The warning log references `e`, but the `except` clause binds nothing. So the
moment a non-JSON response is parsed, the handler itself raises
`NameError: name 'e' is not defined`. This masks the real parse error and
crashes `analyze()` instead of degrading gracefully to an empty
`ToolConnectionMap` as the surrounding code clearly intends.
Reproduced traceback (LLM mocked to return `"this is not json at all"`):
```text
File ".../tool_connection_analyzer.py", line 136, in analyze
response_json = json.loads(clean_json_text.strip())
...
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
During handling of the above exception, another exception occurred:
File ".../tool_connection_analyzer.py", line 141, in analyze
e,
^
NameError: name 'e' is not defined
```
**Solution:**
Bind the caught exception with `as e` so the handler can log the real parse
error and return an empty `ToolConnectionMap` as designed:
```python
except json.JSONDecodeError as e:
logging.warning(...)
return ToolConnectionMap(stateful_parameters=[])
```
This is a one-character fix; the surrounding logging and fallback behaviour are
unchanged. After the fix, the same input logs the underlying parse error and
returns `ToolConnectionMap(stateful_parameters=[])` without raising.
### Testing Plan
**Unit Tests:**
- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.
This branch of `analyze()` previously had zero coverage, so I added
`tests/unittests/tools/environment_simulation/test_tool_connection_analyzer.py`
covering:
- the malformed-JSON path (regression guard — fails with `NameError` on the
pre-fix code, passes after the fix),
- the valid-JSON path, and
- the Markdown code-fence stripping path.
`pytest` summary (low parallelism, as run locally):
```text
$ pytest tests/unittests/tools/environment_simulation/ -n2
======================= 12 passed, 11 warnings in 1.64s ========================
```
```text
$ pytest tests/unittests/tools/environment_simulation/test_tool_connection_analyzer.py -n0 -v
...test_malformed_json_returns_empty_map_without_crashing PASSED
...test_valid_json_is_parsed_into_connection_map PASSED
...test_fenced_json_is_stripped_before_parsing PASSED
======================== 3 passed, 5 warnings in 0.57s =========================
```
Verified the regression test fails on the unfixed code with the exact
`NameError: name 'e' is not defined` and passes after the fix. `isort` and
`pyink` report no changes on the modified files.
**Manual End-to-End (E2E) Tests:**
Reproduced directly against the analyzer with a mocked LLM:
```python
analyzer = ToolConnectionAnalyzer(llm_name=..., llm_config=...)
# LLM mocked to return a non-JSON string.
result = await analyzer.analyze([some_tool])
```
- Before the fix: raises `NameError: name 'e' is not defined`.
- After the fix: logs
`Failed to parse tool connection analysis from LLM... Error: Expecting value: line 1 column 1 (char 0)`
and returns `ToolConnectionMap(stateful_parameters=[])`.
### 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.
- [x] I have manually tested my changes end-to-end.
- [ ] Any dependent changes have been merged and published in downstream modules.
### Additional context
The affected module is marked `@experimental(FeatureName.ENVIRONMENT_SIMULATION)`.
Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5942 from sarathfrancis90:fix-tool-connection-analyzer-jsondecodeerror 536dd569f44eca5eb54d46268cbbcbdb34077374
PiperOrigin-RevId: 955373698
RestApiTool dropped query parameters whose value was falsy, so an explicit
False or 0 was sent identically to an omitted parameter. APIs that
distinguish an absent parameter from an explicit false/zero (boolean
filters, pagination offsets) received incorrect requests. Filter query
parameters on "is not None" instead of truthiness.
Close#6287
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955373236
`_merge_agent_run_pre_3_11()` cancelled its background tasks but did
not await them, so a sibling task could still be mid-`async for` when
the caller ran `aclose()` on the same generator and hit `RuntimeError:
aclose(): asynchronous generator is already running`. That cleanup
race could mask the original sub-agent failure.
Close#5297
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955369097
Serialize the fingerprint with sorted JSON keys, exclude_none, and a
stable tool order so reordered tools or SDK field drift no longer change
the hash. Existing sessions take a one-time cache miss while the stored
fingerprint is recomputed.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955368831
The any_of dedup loop in `_parse_schema_from_parameter` called
`Schema.model_dump_json` twice per Union member — once for the membership
check and once for the set add. Compute the JSON key once and reuse it,
halving Schema serialization for every tool param with `Optional`/`Union`
type on every parse.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955361274
Five tests passed vacuously. The plugin logs and swallows exceptions from
shutdown() and from on_event_callback, so mocks that violated the real
collaborator contract aborted the code under test instead of failing the test.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955359560
Restore private `_scoped_failure_counters` and `_lock` properties
on `ReflectAndRetryToolPlugin` for backward compatibility.
Keep scoped failure counters after creation.
Co-authored-by: Jason Zhang <jasoncz@google.com>
PiperOrigin-RevId: 955323115
Both executors now publish the terminal canceled status update through one
shared helper, so tasks/cancel reaches the canceled state instead of failing
with an internal error.
final=True in that helper is load-bearing but is not covered by CI. a2a-sdk
0.3.x treats a status update as terminal only when final=True; 1.x removed the
field and infers finality from the canceled state. uv.lock resolves 1.1.0, so
no test exercises the 0.3.x branch -- setting final=False here would pass the
entire suite while hanging every 0.3.x server until it times out. 0.3.x was
verified by hand against 0.3.12, 0.3.20, 0.3.22 and 0.3.26; a 0.3.x CI job is
the only thing that would keep it verified.
execute() still deliberately does not catch asyncio.CancelledError: it is a
BaseException, so it already passes through "except Exception" untouched, and
catching it would publish a failed status update racing the canceled one.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955278982
The agent card fetch could only be authenticated via a pre-built httpx
client with credentials fixed at construction time; request_interceptors
only wrap send_message, so the card GET stayed unauthenticated.
Add card_request_interceptors on A2aRemoteAgentConfig (symmetric with
request_interceptors). Each CardRequestInterceptor.before_request is an
async hook returning a typed A2aCardRequestConfig whose headers are injected
into the card request.
PiperOrigin-RevId: 955226237
Merge https://github.com/google/adk-python/pull/6106
Make the auto-detect probe single-flight per tool instance. The first cold caller detects the datastore mode and caches it; concurrent callers re-check the cached mode after the lock and go straight to the detected mode. The successful CHUNKS case is cached too, since the result mode is a datastore property rather than a query property.
Closes: #6101
PiperOrigin-RevId: 954907232
Fixes failing unit test - there was a false negative in "auto" mode when a certificate was present
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 954884006
Harden BigQueryAgentAnalyticsPlugin so it fails closed around formatter and
parser output, redacts sensitive mappings, diagnostic text, signed URIs, and
raw prompt text before inline or GCS storage, validates existing table schema
type and mode recursively at startup, and reports shutdown success only after
all owned work, clients, and executors are drained or closed. Queue accounting
is O(1), JSON nesting is bounded consistently across Python versions, and remote
drains are created and finalized on their owning event loop.
Co-authored-by: Haiyuan Cao <haiyuan@google.com>
PiperOrigin-RevId: 954858186
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
Transforms connection URIs conditionally based on mtls.should_use_mtls_endpoint() and client_cert checks, addressing the mTLS implementation guidelines safely.
PiperOrigin-RevId: 954825139