The `StreamingResponseAggregator.close()` method previously returned `None` if it didn't accumulate text or parts, such as for safety blocks or pure function calls. This caused clients (e.g., Vertex AI Reasoning Engine) to hang indefinitely waiting for a `partial=False` termination frame, and caused loops to break prematurely.
This fix ensures `close()` always returns a final `LlmResponse(partial=False)` as long as a response exists, carrying over any `error_code`, `error_message`, and `usage_metadata`, regardless of whether `PROGRESSIVE_SSE_STREAMING` is enabled. Added parameterized unit tests to verify behavior across both streaming modes.
Fixes#3754
Change-Id: I40d3b4a14cf36e830454d1a0432786de2e8aa3c3
The --remote flag is not supported by gh repo fork when a repository argument (google/adk-python) is provided. Removing it ensures the fork is successfully created in GitHub Actions without causing workflow failures.
Change-Id: Ied687ee512bb37c1af9f3a7ff8b005bef1afd26f
Constrain resolved file paths to the project root so the write, read, and
delete tools cannot escape it via `..` segments or absolute paths.
Change-Id: I3881c230fbc48cda1bca8a75b1e822eecccb934c
- Fixes repository not found errors when pushing the fix branch in the issue-fix workflow by query-inspecting the authenticated bot's username and ensuring the fork exists via gh repo fork.
- Also updates the adk-issue-analyze skill output template to use a details tag for collapsible section and restructure questions.
Change-Id: If87b4ddbe897b9338aa6ee78221709e8777b0045
Enables issue analysis workflow to trigger on comments posted to pull requests. This is done by removing the `!github.event.issue.pull_request` check from the GHA issue analysis workflow definition, since GitHub models PR comments under the `issue_comment` event. The fix implementation workflow remains restricted to issues to prevent PR-on-PR loops and permission issues.
- Update scripts/run_antigravity.py to include comments and review comments in JSON payloads fetched by helper tools.
- Update adk-issue and adk-issue-analyze skills to prevent routing conflicts when /adk-issue-analyze is explicitly requested.
- Update branch validation in issue-fix workflow to fail if the agent does not successfully create and checkout fix/issue-<number>.
- Update adk-issue-fix skill branch template to match expected GHA workflow fallback pattern.
Change-Id: I34405266bb6b11cc4ad18878ef932bb46677c89d
test_metrics reads a process-global meter provider, so a leaked agent
invocation from an earlier test can add a stray data point and trip the
len() assertions (flaky on python 3.10). Filter extracted points to the
agent under test.
Change-Id: I02409144620a635bc22f9fb6826ae68e8b6c6cda
- Exposed history_config in RunConfig.
- Mapped history_config to LLM live connect request configuration.
- Generalized history connection logic to automatically inject `initial_history_in_client_content = True` when seeding history on a fresh connection for both Gemini API and Vertex AI backends.
- Updated and added comprehensive unit tests to verify history configuration behaviour.
TAG=agy
CONV=822f8c76-9099-4f01-a2b8-10a7de0d61a2
Change-Id: Ib532626d5d7d887b17664567aed94ba09ad90b33
- Adds secure command policy to the LocalAgentConfig in scripts/run_antigravity.py
that denies unsafe command executions and checks for shell injection.
- Allows both `gh` and `git` commands to be executed by the agent runner.
- Adds custom `fetch_github_issue` and `fetch_github_pr` Python tools to the
Antigravity agent runner configuration, using `curl` to enable direct JSON
metadata fetches from GitHub without requiring a configured `gh` CLI environment.
- Introduces the --show-steps CLI flag to scripts/run_antigravity.py
to output intermediate thoughts, tool calls, and tool results (default off).
- Updates .github/workflows/issue-analyze.yml to capture stdout from the
runner script, run automatic triage for any user's opened issues, and post
the report to the triggering GitHub issue as a comment via the `gh` CLI tool.
- Updates the adk-issue-analyze and adk-pr-triage skills to prefer using the
custom `fetch_github_issue` Python tool over raw `gh` command lines, and
strictly enforces that the issue-analyze skill is read-only and must not edit files.
Change-Id: I58a9c64f0680a56d07b9877cbcb9ffe027afeee2
Installs the public google-antigravity SDK via pip and executes a runner
python script scripts/run_antigravity.py to run any antigravity prompts.
This replaces the Node-based @google/antigravity CLI tool, and allows general
executions via Python.
Change-Id: Iade60816b4613567e261934a46285d7933adfc00
`Runner._find_agent_to_run` is annotated `-> BaseAgent` and documents that it
"falls back to root agent if no suitable agents are found". But on a resumable
resume from a function response it returned
`root_agent.find_agent(event.author)` unconditionally, and `find_agent` returns
None when `event.author` is not a node in the agent tree (host integrations
commonly hydrate resumed or external events with author='user'). That None
then flowed into `build_node(agent_to_run)` in `run_async`, raising
"ValueError: Invalid node type: <class 'NoneType'>".
Fix: in the resumable branch, only short-circuit when `find_agent` resolves the
author; otherwise fall through to the existing event scan and root-agent
fallback. This matches the non-resumable branch and the method's documented
contract, so the change only turns a guaranteed crash into the documented
fallback.
Also de-nests the TestRunnerFindAgentToRun tests, which were accidentally
defined inside a module-level test function and never collected, and adds two
regression tests.
Change-Id: I7d64187be5e6443a185a20b0ad08bea3ae017fb1
Three related reliability/observability fixes to the BigQuery Agent
Analytics plugin.
1. Dropped-event observability. BigQuery logging is best-effort: events
are dropped when the in-memory queue overflows or a write ultimately
fails, and only a log line records the loss. Track dropped rows in
BatchProcessor by reason (queue_full, arrow_prep_failed,
retry_exhausted, non_retryable, unexpected_error), include the
running total in each drop log line, and expose the counts via
BatchProcessor.get_drop_stats()/dropped_event_count and an
aggregating BigQueryAgentAnalyticsPlugin.get_drop_stats() so a host
can poll them and export to its own monitoring.
2. Cross-region Storage Write API routing. The AppendRows streaming RPC
does not auto-populate the request-routing header, so writes to a
dataset outside the US multiregion could fail with a "session not
found" / stream-not-found error and silently drop every row. Set
x-goog-request-params: write_stream=<stream> on the append_rows call
so the request reaches the region that owns the write stream.
US-multiregion behavior is unchanged.
3. Stop exporting plugin-owned OTel spans. When Agent Engine telemetry
is enabled (GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY=true) with
Cloud Trace export on the global tracer provider, the plugin's
ID-carrier spans were exported alongside the framework's real spans,
producing a duplicate span for every instrumented operation. The
plugin now tracks span_id / trace_id on its own contextvar stack
without creating OTel spans; trace_id is inherited from the ambient
span, so BigQuery rows still join to Cloud Trace by trace_id and the
LLM/tool span_id-sharing contracts are preserved.
All paths covered by unit tests.
Change-Id: Ia7b73d816b14c574ef856a4c88c57243f6f38f7f
Merge test_event_message.py and test_workflow_events.py into test_event.py to simplify the test suite and keep all Event class behavior tests in a single place.
Change-Id: I99223f44e54fce8d37c5d632358e4343a8dc526d
The local artifact service wrote to a single <agents_dir>/.adk/artifacts
root shared by all agents, while session storage is per-agent under
<agents_dir>/<agent>/.adk. Route artifacts the same way so each agent's
artifacts live next to its session.db.
For adk web/api_server this moves the default artifact location to
<agents_dir>/<agent>/.adk/artifacts. To stay backward compatible,
PerAgentFileArtifactService falls back to reading the old shared root on
a miss, so existing artifacts keep loading; new writes go per-agent
(copy-on-write migration). create_artifact_service_from_options logs a
WARNING when a legacy shared root is found, pointing users at the manual
move (the users/ dir into <agent>/.adk/artifacts). adk run is unaffected
(it already resolved to the per-agent path).
Also harden _resolve_agent_dir to use Path.is_relative_to instead of a
string-prefix check, which accepted prefix-sharing siblings (e.g.
app_name "../agents_evil" under an "agents" root). This guards both the
artifact and session per-agent paths.
Change-Id: Ib1b911fa35de00ebf8335a0825a03bb98dfab336
## What
The v0 session schema stored event actions as pickled blobs. The migration helper reads raw bytes via `SELECT * FROM events` and previously used `pickle.loads(...)` directly.
This PR replaces the default load path with a restricted unpickler allowlist for builtin containers/primitives, standard ADK `EventActions` payloads, nested ADK core action types (`AuthConfig`, `ToolConfirmation`, `EventCompaction`), and the `google.genai.types.Content` / `Part` dependency classes that normal compaction payloads require.
It also adds an explicit trust toggle for legacy databases that contain custom Python objects in `state_delta` or other `Any` fields:
- Python API: `migrate(..., allow_unsafe_unpickling=True)`
- Migration runner: `upgrade(..., allow_unsafe_unpickling=True)`
- CLI: `adk migrate session --allow_unsafe_unpickling ...`
- Direct script: `--allow_unsafe_unpickling` / `--allow-unsafe-unpickling`
## Why
`pickle` is not safe for untrusted inputs. Migration tooling often runs against restored/backed-up DB files or shared storage; failing closed by default reduces the blast radius if the source DB contents are compromised.
The opt-in flag keeps compatibility for users who trust their source database and need the original unsafe pickle behavior for custom legacy objects.
## Associated Issue / Background
No existing GitHub issue is linked. This was found while reviewing the v0-to-v1 migration path for unsafe deserialization risks in legacy session data.
## Compatibility / fail-closed boundary
Normal v0 `EventActions` payloads made from primitive/container fields continue to migrate. The allowlist now also covers common nested ADK action models requested during review, including requested auth configs, requested tool confirmations, and event compaction content.
Payloads that require globals outside the explicit allowlist still fail closed by default: the migration logs a warning and falls back to empty `EventActions()` for that event. Users can opt into the previous unsafe pickle behavior only when they trust the source database.
## Verification
- `uv run pytest tests/unittests/sessions/migration/test_migration.py`
- `22 passed, 4 warnings`
- `uv run mypy src/google/adk/sessions/migration/migrate_from_sqlalchemy_pickle.py src/google/adk/sessions/migration/migration_runner.py`
- `Success: no issues found in 2 source files`
- `uv run pre-commit run --files src/google/adk/sessions/migration/migrate_from_sqlalchemy_pickle.py src/google/adk/sessions/migration/migration_runner.py src/google/adk/cli/cli_tools_click.py tests/unittests/sessions/migration/test_migration.py`
- passed
- `git diff --check`
- passed
Merge https://github.com/google/adk-python/pull/5866
Change-Id: I2f66069cb301887fbf7147dbe758b60ec2242d80
Fix for #5603 - Validate normalized relative paths in skill extraction code to prevent directory traversal via malicious GCS skill resource names.
## Problem
The _build_wrapper_code method in skill_toolset.py generates Python code that extracts skill files into a temporary directory. The relative paths from GCS blob names are used directly with os.path.join(td, rel_path) without validation.
A malicious skill resource name containing ../ (e.g., references/../../etc/cron.d/evil) would resolve outside the temporary directory, enabling arbitrary file writes. Combined with runpy.run_path(), this creates a path-traversal-to-RCE chain (CWE-22 / Zip Slip variant).
## Fix
Added path normalization and validation before file extraction in the generated wrapper code:
1. Normalize the relative path with os.path.normpath()
2. Reject paths starting with .. (parent directory traversal)
3. Reject absolute paths via os.path.isabs()
4. Use os.path.abspath(td) for the base directory to prevent bypasses
## Testing
### Unit Tests Added
Added tests/unittests/tools/test_skill_path_traversal.py with 5 tests.
Fixes#5603
Merge https://github.com/google/adk-python/pull/5927
Change-Id: I7dc2f92e863785ccb1d6ea0ed65b0b01c537fabc
Update the generated Gerrit PR description to include `closes <Issue link>` footers when the PR is linked to GitHub issues, and ensure this footer is preserved during squashing.
Change-Id: Ia0dc66b1e2923d07ee42561063d290847c7f29d5
Add a dedicated test module for the Event class, which previously had
only incidental coverage. Covers is_final_response(), get_function_calls(),
get_function_responses(), has_trailing_code_execution_result(), and
automatic id generation, including edge cases such as events with no
content, empty parts lists, and the skip_summarization /
long_running_tool_ids early-return overrides.
Merge https://github.com/google/adk-python/pull/5948
Change-Id: If05c49f82c7bb73d8c39be548434672497a770c2
Centralize text extraction and schema validation for rehydrated output via a shared helper `extract_text_from_content`. If a stored output fails validation against the node's schema due to schema drift, gracefully fallback to parsing unvalidated JSON to avoid blocking resumption, rather than crashing.
Merge https://github.com/google/adk-python/pull/5909
Change-Id: I2a138884d42a82b961285c0784eba46327d47e31
The compaction summarizer fed only message text to the LLM, dropping
agent thoughts and tool calls/responses. Ablation found those carry the
analysis and evidence a summary must preserve, so include them, skip a
prior compaction's own thought, and reiterate the user request in the
default prompt to reduce drift.
Change-Id: Ibc09eaa6237c20ede126da263b428dac8ff3e40a
### Link to Issue
- Related: #5487
**Problem:**
`to_a2a(workflow)` fails because `AgentCardBuilder` requires field `sub_agents` which `Workflow` does not have.
**Solution:**
- Make `to_a2a()` and `AgentCardBuilder` accept `Workflow` (the v2 graph orchestrator) as a root, not just `BaseAgent`. Previously crashed with`RuntimeError: 'Workflow' object has no attribute 'sub_agents'`.
- Tighten the public type contract to `BaseAgent | Workflow` and reject other `BaseNode` subtypes (e.g. `FunctionNode`, `JoinNode`) at call time with `TypeError`. They previously produced a degenerate "custom agent" card silently.
### Testing Plan
**tests/unittests/a2a/utils/test_agent_card_builder.py** — 9 new tests:
- test_get_agent_type_workflow — returns 'graph_workflow' for the new v2 Workflow.
- test_get_agent_skill_name_workflow — returns 'workflow' for Workflow.
- test_init_rejects_function_node — AgentCardBuilder(agent=FunctionNode(...)) raises TypeError (regression coverage for the runtime guard).
- test_init_rejects_arbitrary_object — AgentCardBuilder(agent="...") raises TypeError.
- test_build_succeeds_for_llm_agent — regression coverage that the original BaseAgent path still works after the type narrowing.
- test_build_succeeds_for_workflow_with_llm_agent_node — exact OP repro shape, end-to-end through build().
- test_build_succeeds_for_workflow_with_output_schema_node — covers the output_schema shape mentioned in the issue.
- test_build_succeeds_for_empty_workflow — degenerate but valid case (no edges).
- test_get_workflow_description_workflow_with_nodes — verifies graph nodes appear in the description string.
- test_get_workflow_description_empty_workflow — returns None when no nodes.
**tests/unittests/a2a/utils/test_agent_to_a2a.py** — 2 new tests, 1 rewritten:
- test_to_a2a_succeeds_for_workflow (new) — end-to-end through the actual Starlette lifespan (the exact code path that crashed in the OP's repro).
- test_to_a2a_rejects_function_node (new) — to_a2a(FunctionNode(...)) raises TypeError at call time.
- test_to_a2a_rejects_non_agent_non_workflow (rewrote existing test_to_a2a_with_invalid_agent_type) — now asserts TypeError raised eagerly at to_a2a() call time instead of AttributeError raised lazily during request handling. This is a deliberate behavior change, not a regression: the old test encoded buggy lazy-failure UX where misuse only surfaced when a client hit the endpoint.
**Unit Tests:**
- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.
_Please include a summary of passed `pytest` results._
ran `uv run pytest tests/unittests`
`6275 passed, 14 skipped, 25 xfailed, 10 xpassed, 2532 warnings `
### 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.
Merge https://github.com/google/adk-python/pull/5710
Change-Id: I2330b2f3036cfd99c89b5d5c492f1c46c275974f
_get_transfer_targets read peer_agent.mode directly while iterating the
parent's sub_agents. mode only exists on LlmAgent, so a peer that is a
plain BaseAgent subclass raised AttributeError and broke
transfer_to_agent. Mirror the hasattr guard already used in the
sub_agents branch.
Closes#5863
Change-Id: Ia0bcfcae43df81f469867d9e1e7e356db9f34fb6
Closes: #5592
Problem:
BaseSessionService has no public method to read user-scoped state without an active session_id. Callers that need to bootstrap user context before a new session exists are forced to call the expensive list_sessions or maintain a separate process-level cache as a workaround.
Solution:
Add get_user_state(app_name, user_id) -> dict[str, Any] to BaseSessionService. Implemented in InMemorySessionService, DatabaseSessionService, and SqliteSessionService. VertexAiSessionService raises NotImplementedError because the Vertex AI Agent Engine API does not expose user state independently of a session. The default in BaseSessionService also raises NotImplementedError to preserve backward compatibility for existing custom subclasses.
Keys are returned without the user: prefix, consistent with how user state is stored internally (the prefix is applied by the state-merging layer).
Merge https://github.com/google/adk-python/pull/5596
Change-Id: Id1015034c62810aafd2b2411a0376742bb80e8c8
Merge https://github.com/google/adk-python/pull/5569
**Problem:**
Users needed to be able to add additional scopes to `GoogleApiToolset` instances to avoid 401 errors when the auto-selected scope was insufficient. They also needed to be able to specify a custom discovery document URL for cases where the standard Google discovery service is not used or a specific version is needed.
**Solution:**
- Added `additional_scopes` parameter to `GoogleApiToolset` to allow appending scopes to the default one derived from the discovery document.
- Added `discovery_url` parameter to `GoogleApiToolset` and `GoogleApiToOpenApiConverter` to allow specifying a custom discovery URL.
GitOrigin-RevId: 104bcaad2df98cccbdbdae3343d165855d7487b6
Change-Id: Iceb3835a42cbcc4e2672e735454da6f0166802bb
Update the adk-pr-triage skill instructions for local review to ensure the repository state is correct before squashing, and to prevent the loss of the original author's attribution.
- Added an explicit `git fetch origin main` step before PR checkout.
- Modified the squash instructions to capture and apply the original author via the `--author` flag during the squashed commit.
Change-Id: I646a6e8280e91033806f65d34799347d73b5e063
Resolves protocol handling discrepancies when connecting to Gemini 3.1 Flash Live models:
- Sets initial_history_in_client_content=True when seeding conversation history during connection handshake.
- Appends turn_complete=True on the final history turn during setup.
- Iterates sequentially through all parts of model_turn to unpack multiplexed audio and text responses.
- Prunes unsupported proactivity and affective dialogue configurations when assembling LiveConnectConfig.
Change-Id: I2d0ff38d8a6eb40ea17b37f65a4ddd093230842c
_accept_convenience_kwargs already routes construction kwargs to a
subclass-declared `message` field, but the property/setter always used
`content`, so reads of that field returned the content alias instead of
the stored value. Defer to the field when present; base Event behavior
(message aliases content) is unchanged.
Change-Id: I8dd492407b8117ad9c6a90fb24269a03dac49ac0
Monkey-patches in-scope Python functions to emit an OpenTelemetry span
per call, capturing args/return/exception as span attributes. Scope is
auto-discovered from the agent tree. No-op when the tracer is not
recording.
Change-Id: I103678b2189a75dcd74af51deb75eb4346c20551
- Support PEP 604 unions in direct parameter parser.
- Fix a bug where collapsing simple unions (e.g., `Optional[list[T]]`) lost
nested schema properties (like `items` or `properties`) by replacing the
parent schema with the collapsed inner schema instead of just copying its
type.
- Update `test_required_fields_set_in_json_schema_fallback` to use
`tuple[str, ...]` to ensure the fallback path remains tested.
Change-Id: Idc1cb55e265ba888c03aa923f60d2d4b3d1ae131
Verify pull request assignment against the active GitHub user to prevent
concurrent triage overhead and restrict local checkout to the assigned owner.
Additionally, stream the fetched PR metadata directly via stdout instead of
writing to a local workspace cache file to prevent disk clutter and simplify
assistant JSON parsing.
Change-Id: I9408fba5c200cca5814afb7223302eb849c0b319