Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 17 additions & 1 deletion python/packages/openai/agent_framework_openai/_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1590,7 +1590,10 @@ def _prepare_message_for_openai(
)
if function_call:
all_messages.append(function_call)
case "function_approval_response" | "function_approval_request":
case "function_approval_request":
# The model's approval request is a server-issued item; under a
# continuation the server already has it, so replaying it inline
# duplicates it (#3295). Same treatment as function_call above.
if request_uses_service_side_storage:
continue
prepared = self._prepare_content_for_openai(
Expand All @@ -1600,6 +1603,19 @@ def _prepare_message_for_openai(
)
if prepared:
all_messages.append(prepared)
case "function_approval_response":
# The approval response is the user's decision that resumes an
# approval-paused run. It references the prior request by id and is
# not itself a server-stored item, so it must reach the model even
# under service-side storage; skipping it leaves the run paused
# forever (#7125). Same treatment as function_result above.
prepared = self._prepare_content_for_openai(
message.role,
content,
replays_local_storage=replays_local_storage,
)
if prepared:
all_messages.append(prepared)
case "mcp_server_tool_call" | "mcp_server_tool_result":
# Hosted MCP call/result contents serialize as a single
# top-level mcp_call input item; the result side emits an
Expand Down
28 changes: 28 additions & 0 deletions python/packages/openai/tests/openai/test_openai_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2575,6 +2575,34 @@ def test_prepare_message_for_openai_with_function_approval_response() -> None:
assert prepared_message["approve"] is True


def test_prepare_message_for_openai_keeps_approval_response_under_service_side_storage() -> None:
"""The approval response resumes an approval-paused run and must survive service-side storage.

It references the prior approval request by id and is not itself a server-stored item, so
stripping it (as the #3295 duplicate-item fix used to) leaves the run paused forever (#7125).
"""
client = OpenAIChatClient(model="test-model", api_key="test-key")

function_call = Content.from_function_call(
call_id="call_789",
name="execute_command",
arguments='{"command": "ls"}',
)
approval_response = Content.from_function_approval_response(
approved=True,
id="approval_003",
function_call=function_call,
)
message = Message(role="user", contents=[approval_response])

result = client._prepare_message_for_openai(message, request_uses_service_side_storage=True)

assert len(result) == 1
assert result[0]["type"] == "mcp_approval_response"
assert result[0]["approval_request_id"] == "approval_003"
assert result[0]["approve"] is True


def test_prepare_message_for_openai_includes_reasoning_with_function_call() -> None:
"""Test _prepare_message_for_openai includes reasoning items alongside function_calls.

Expand Down