Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c305b88
feat: implement secure token handling via request_state
timof1308 Jan 19, 2026
7de5bb3
feat: Add warning for non-primitive state values that may not seriali…
timof1308 Jan 19, 2026
adbf231
refactor: Introduce `HeaderProvider` type and modularize header gener…
timof1308 Jan 19, 2026
9c1debc
feat: Add strict mode to `create_session_state_header_provider` to ra…
timof1308 Jan 19, 2026
12da580
feat: Introduce `state_header_strict` config for strict state header …
timof1308 Jan 19, 2026
6c43eb3
applied autoformat.sh
timof1308 Jan 19, 2026
376c719
feat: Add RFC 7230 compliant header validation and sanitization
timof1308 Jan 19, 2026
d054fae
refactor: add future annotations import to types and internal modules.
timof1308 Jan 19, 2026
226c64e
refactor: Publicize header name validation, refine header name regex,…
timof1308 Jan 19, 2026
641eb7b
applied autoformat.sh
timof1308 Jan 19, 2026
a0f80b2
applied ./autoformat.sh
timof1308 Jan 19, 2026
75e1c5f
apply autoformat after rebase
timof1308 Jan 19, 2026
6b0ae54
test(mcp): Fix Mock InvocationContext to include request_state
timof1308 Jan 20, 2026
cc4b775
Fix test_fast_api.py mocks to match new run_async signature
timof1308 Jan 21, 2026
95ec777
apply autoformat.sh
timof1308 Jan 21, 2026
64b9546
fix(mcp): Remove duplicate create_session_state_header_provider
timof1308 Jan 21, 2026
e9b837a
docs: Clarify `ReadOnlyContext.state` property as `MappingProxyType` …
timof1308 Jan 21, 2026
e18484d
docs: update `ReadOnlyContext.state` to use `MappingProxyType` with a…
timof1308 Jan 21, 2026
19c95b0
Remove the `_HEADER_NAME_FORBIDDEN` constant
timof1308 Jan 21, 2026
f2216c8
remove unused import of `get_mcp_auth_headers`
timof1308 Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion contributing/samples/gepa/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
from tau_bench.types import EnvRunResult
from tau_bench.types import RunConfig
import tau_bench_agent as tau_bench_agent_lib

import utils


Expand Down
1 change: 0 additions & 1 deletion contributing/samples/gepa/run_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from absl import flags
import experiment
from google.genai import types

import utils

_OUTPUT_DIR = flags.DEFINE_string(
Expand Down
8 changes: 8 additions & 0 deletions src/google/adk/agents/invocation_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ class InvocationContext(BaseModel):
agent_states: dict[str, dict[str, Any]] = Field(default_factory=dict)
"""The state of the agent for this invocation."""

request_state: dict[str, Any] = Field(default_factory=dict)
"""The ephemeral state of the request.

This state is not persisted to the session and is only available for the
current invocation. It is used to pass sensitive information like tokens
that should not be stored in the session state.
"""

end_of_agents: dict[str, bool] = Field(default_factory=dict)
"""The end of agent status for each agent in this invocation."""

Expand Down
17 changes: 15 additions & 2 deletions src/google/adk/agents/readonly_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@

from __future__ import annotations

from collections import ChainMap
from types import MappingProxyType
from typing import Any
from typing import Mapping
from typing import Optional
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -52,8 +54,19 @@ def agent_name(self) -> str:

@property
def state(self) -> MappingProxyType[str, Any]:
"""The state of the current session. READONLY field."""
return MappingProxyType(self._invocation_context.session.state)
"""The state of the current session. READONLY field.

Note: This property returns a merged view of ephemeral request_state and
persistent session.state using ChainMap. Changes to the underlying
request_state or session.state dictionaries will be reflected through
this view, but direct writes through this property are prevented.
"""
return MappingProxyType(
ChainMap(
self._invocation_context.request_state,
self._invocation_context.session.state,
)
)

@property
def session(self) -> Session:
Expand Down
3 changes: 3 additions & 0 deletions src/google/adk/cli/adk_web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ class RunAgentRequest(common.BaseModel):
new_message: types.Content
streaming: bool = False
state_delta: Optional[dict[str, Any]] = None
request_state: Optional[dict[str, Any]] = None
# for resume long-running functions
invocation_id: Optional[str] = None

Expand Down Expand Up @@ -1498,6 +1499,7 @@ async def run_agent(req: RunAgentRequest) -> list[Event]:
session_id=req.session_id,
new_message=req.new_message,
state_delta=req.state_delta,
request_state=req.request_state,
)
) as agen:
events = [event async for event in agen]
Expand Down Expand Up @@ -1527,6 +1529,7 @@ async def event_generator():
session_id=req.session_id,
new_message=req.new_message,
state_delta=req.state_delta,
request_state=req.request_state,
run_config=RunConfig(streaming_mode=stream_mode),
invocation_id=req.invocation_id,
)
Expand Down
13 changes: 13 additions & 0 deletions src/google/adk/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ async def run_async(
invocation_id: Optional[str] = None,
new_message: Optional[types.Content] = None,
state_delta: Optional[dict[str, Any]] = None,
request_state: Optional[dict[str, Any]] = None,
run_config: Optional[RunConfig] = None,
) -> AsyncGenerator[Event, None]:
"""Main entry method to run the agent in this runner.
Expand All @@ -475,6 +476,7 @@ async def run_async(
interrupted invocation.
new_message: A new message to append to the session.
state_delta: Optional state changes to apply to the session.
request_state: Optional ephemeral state for the request.
run_config: The run config for the agent.

Yields:
Expand Down Expand Up @@ -519,6 +521,7 @@ async def _run_with_trace(
invocation_id=invocation_id,
run_config=run_config,
state_delta=state_delta,
request_state=request_state,
)
if invocation_context.end_of_agents.get(
invocation_context.agent.name
Expand All @@ -532,6 +535,7 @@ async def _run_with_trace(
new_message=new_message, # new_message is not None.
run_config=run_config,
state_delta=state_delta,
request_state=request_state,
)

async def execute(ctx: InvocationContext) -> AsyncGenerator[Event]:
Expand Down Expand Up @@ -1260,6 +1264,7 @@ async def _setup_context_for_new_invocation(
new_message: types.Content,
run_config: RunConfig,
state_delta: Optional[dict[str, Any]],
request_state: Optional[dict[str, Any]] = None,
) -> InvocationContext:
"""Sets up the context for a new invocation.

Expand All @@ -1268,6 +1273,7 @@ async def _setup_context_for_new_invocation(
new_message: The new message to process and append to the session.
run_config: The run config of the agent.
state_delta: Optional state changes to apply to the session.
request_state: Optional ephemeral state for the request.

Returns:
The invocation context for the new invocation.
Expand All @@ -1277,6 +1283,7 @@ async def _setup_context_for_new_invocation(
session,
new_message=new_message,
run_config=run_config,
request_state=request_state,
)
# Step 2: Handle new message, by running callbacks and appending to
# session.
Expand All @@ -1299,6 +1306,7 @@ async def _setup_context_for_resumed_invocation(
invocation_id: Optional[str],
run_config: RunConfig,
state_delta: Optional[dict[str, Any]],
request_state: Optional[dict[str, Any]] = None,
) -> InvocationContext:
"""Sets up the context for a resumed invocation.

Expand All @@ -1308,6 +1316,7 @@ async def _setup_context_for_resumed_invocation(
invocation_id: The invocation id to resume.
run_config: The run config of the agent.
state_delta: Optional state changes to apply to the session.
request_state: Optional ephemeral state for the request.

Returns:
The invocation context for the resumed invocation.
Expand All @@ -1333,6 +1342,7 @@ async def _setup_context_for_resumed_invocation(
new_message=user_message,
run_config=run_config,
invocation_id=invocation_id,
request_state=request_state,
)
# Step 3: Maybe handle new message.
if new_message:
Expand Down Expand Up @@ -1377,6 +1387,7 @@ def _new_invocation_context(
new_message: Optional[types.Content] = None,
live_request_queue: Optional[LiveRequestQueue] = None,
run_config: Optional[RunConfig] = None,
request_state: Optional[dict[str, Any]] = None,
) -> InvocationContext:
"""Creates a new invocation context.

Expand All @@ -1386,6 +1397,7 @@ def _new_invocation_context(
new_message: The new message for the context.
live_request_queue: The live request queue for the context.
run_config: The run config for the context.
request_state: The ephemeral state for the request.

Returns:
The new invocation context.
Expand Down Expand Up @@ -1417,6 +1429,7 @@ def _new_invocation_context(
live_request_queue=live_request_queue,
run_config=run_config,
resumability_config=self.resumability_config,
request_state=request_state if request_state is not None else {},
)

def _new_invocation_context_for_live(
Expand Down
2 changes: 2 additions & 0 deletions src/google/adk/tools/mcp_tool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .mcp_session_manager import StreamableHTTPConnectionParams
from .mcp_tool import MCPTool
from .mcp_tool import McpTool
from .mcp_toolset import create_session_state_header_provider
from .mcp_toolset import MCPToolset
from .mcp_toolset import McpToolset

Expand All @@ -32,6 +33,7 @@
'MCPTool',
'McpToolset',
'MCPToolset',
'create_session_state_header_provider',
'SseConnectionParams',
'StdioConnectionParams',
'StreamableHTTPConnectionParams',
Expand Down
Loading