Skip to content

Commit bc2ec77

Browse files
committed
fix: apply history_length for message/send requests
1 parent 051ab20 commit bc2ec77

File tree

3 files changed

+115
-19
lines changed

3 files changed

+115
-19
lines changed

src/a2a/server/request_handlers/default_request_handler.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
UnsupportedOperationError,
4545
)
4646
from a2a.utils.errors import ServerError
47+
from a2a.utils.task import apply_history_length
4748
from a2a.utils.telemetry import SpanKind, trace_class
4849

4950

@@ -118,25 +119,7 @@ async def on_get_task(
118119
raise ServerError(error=TaskNotFoundError())
119120

120121
# Apply historyLength parameter if specified
121-
if params.history_length is not None and task.history:
122-
# Limit history to the most recent N messages
123-
limited_history = (
124-
task.history[-params.history_length :]
125-
if params.history_length > 0
126-
else []
127-
)
128-
# Create a new task instance with limited history
129-
task = Task(
130-
id=task.id,
131-
context_id=task.context_id,
132-
status=task.status,
133-
artifacts=task.artifacts,
134-
history=limited_history,
135-
metadata=task.metadata,
136-
kind=task.kind,
137-
)
138-
139-
return task
122+
return apply_history_length(task, params.history_length)
140123

141124
async def on_cancel_task(
142125
self, params: TaskIdParams, context: ServerCallContext | None = None
@@ -363,6 +346,10 @@ async def push_notification_callback() -> None:
363346

364347
if isinstance(result, Task):
365348
self._validate_task_id_match(task_id, result.id)
349+
if params.configuration:
350+
result = apply_history_length(
351+
result, params.configuration.history_length
352+
)
366353

367354
await self._send_push_notification_if_needed(task_id, result_aggregator)
368355

src/a2a/utils/task.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,33 @@ def completed_task(
7070
artifacts=artifacts,
7171
history=history,
7272
)
73+
74+
75+
def apply_history_length(task: Task, history_length: int | None) -> Task:
76+
"""Applies history_length parameter on task and returns a new task object.
77+
78+
Args:
79+
task: The original task object with complete history
80+
history_length: History length configuration value
81+
82+
Returns:
83+
A new task object with limited history
84+
"""
85+
# Apply historyLength parameter if specified
86+
if history_length is not None and task.history:
87+
# Limit history to the most recent N messages
88+
limited_history = (
89+
task.history[-history_length:] if history_length > 0 else []
90+
)
91+
# Create a new task instance with limited history
92+
return Task(
93+
id=task.id,
94+
context_id=task.context_id,
95+
status=task.status,
96+
artifacts=task.artifacts,
97+
history=limited_history,
98+
metadata=task.metadata,
99+
kind=task.kind,
100+
)
101+
102+
return task

tests/server/request_handlers/test_default_request_handler.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,85 @@ async def test_on_message_send_non_blocking():
836836
assert task.status.state == TaskState.completed
837837

838838

839+
@pytest.mark.asyncio
840+
async def test_on_message_send_limit_history():
841+
task_store = InMemoryTaskStore()
842+
push_store = InMemoryPushNotificationConfigStore()
843+
844+
request_handler = DefaultRequestHandler(
845+
agent_executor=HelloAgentExecutor(),
846+
task_store=task_store,
847+
push_config_store=push_store,
848+
)
849+
params = MessageSendParams(
850+
message=Message(
851+
role=Role.user,
852+
message_id='msg_push',
853+
parts=[Part(root=TextPart(text='Hi'))],
854+
),
855+
configuration=MessageSendConfiguration(
856+
blocking=True,
857+
accepted_output_modes=['text/plain'],
858+
history_length=0,
859+
),
860+
)
861+
862+
result = await request_handler.on_message_send(
863+
params, create_server_call_context()
864+
)
865+
866+
# verify that history_length is honored
867+
assert result is not None
868+
assert isinstance(result, Task)
869+
assert result.history is not None and len(result.history) == 0
870+
assert result.status.state == TaskState.completed
871+
872+
# verify that history is still persisted to the store
873+
task = await task_store.get(result.id)
874+
assert task is not None
875+
assert task.history is not None and len(task.history) > 0
876+
877+
878+
@pytest.mark.asyncio
879+
async def test_on_task_get_limit_history():
880+
task_store = InMemoryTaskStore()
881+
push_store = InMemoryPushNotificationConfigStore()
882+
883+
request_handler = DefaultRequestHandler(
884+
agent_executor=HelloAgentExecutor(),
885+
task_store=task_store,
886+
push_config_store=push_store,
887+
)
888+
params = MessageSendParams(
889+
message=Message(
890+
role=Role.user,
891+
message_id='msg_push',
892+
parts=[Part(root=TextPart(text='Hi'))],
893+
),
894+
configuration=MessageSendConfiguration(
895+
blocking=True, accepted_output_modes=['text/plain']
896+
),
897+
)
898+
899+
result = await request_handler.on_message_send(
900+
params, create_server_call_context()
901+
)
902+
903+
assert result is not None
904+
assert isinstance(result, Task)
905+
906+
get_task_result = await request_handler.on_get_task(
907+
TaskQueryParams(id=result.id, history_length=0),
908+
create_server_call_context(),
909+
)
910+
assert get_task_result is not None
911+
assert isinstance(get_task_result, Task)
912+
assert (
913+
get_task_result.history is not None
914+
and len(get_task_result.history) == 0
915+
)
916+
917+
839918
@pytest.mark.asyncio
840919
async def test_on_message_send_interrupted_flow():
841920
"""Test on_message_send when flow is interrupted (e.g., auth_required)."""

0 commit comments

Comments
 (0)