Skip to content

Commit 4adfd24

Browse files
authored
Python: Upgrade hosting server dependency and add more type support (#5459)
* Upgrade hosting server dependency and add more type support * Comments
1 parent 932cedd commit 4adfd24

4 files changed

Lines changed: 1492 additions & 56 deletions

File tree

python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py

Lines changed: 287 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,35 @@
2828
)
2929
from azure.ai.agentserver.responses.hosting import ResponsesAgentServerHost
3030
from azure.ai.agentserver.responses.models import (
31+
ApplyPatchToolCallItemParam,
32+
ApplyPatchToolCallOutputItemParam,
33+
ComputerCallOutputItemParam,
3134
ComputerScreenshotContent,
3235
CreateResponse,
3336
FunctionCallOutputItemParam,
3437
FunctionShellAction,
38+
FunctionShellCallItemParam,
3539
FunctionShellCallOutputContent,
3640
FunctionShellCallOutputExitOutcome,
41+
FunctionShellCallOutputItemParam,
42+
Item,
43+
ItemCodeInterpreterToolCall,
44+
ItemComputerToolCall,
45+
ItemCustomToolCall,
46+
ItemCustomToolCallOutput,
47+
ItemFileSearchToolCall,
48+
ItemFunctionToolCall,
49+
ItemImageGenToolCall,
50+
ItemLocalShellToolCall,
51+
ItemLocalShellToolCallOutput,
52+
ItemMcpApprovalRequest,
53+
ItemMcpToolCall,
54+
ItemMessage,
55+
ItemOutputMessage,
56+
ItemReasoningItem,
57+
ItemWebSearchToolCall,
3758
LocalEnvironmentResource,
59+
MCPApprovalResponse,
3860
MessageContent,
3961
MessageContentInputFileContent,
4062
MessageContentInputImageContent,
@@ -174,9 +196,11 @@ async def _handle_regular_agent(
174196
context: ResponseContext,
175197
) -> AsyncIterable[ResponseStreamEvent | dict[str, Any]]:
176198
"""Handle the creation of a response for a regular (non-workflow) agent."""
177-
input_text = await context.get_input_text()
199+
input_items = await context.get_input_items()
200+
input_messages = _items_to_messages(input_items)
201+
178202
history = await context.get_history()
179-
messages: list[str | Content | Message] = [*_to_messages(history), input_text]
203+
messages: list[str | Content | Message] = [*_output_items_to_messages(history), *input_messages]
180204

181205
chat_options, are_options_set = _to_chat_options(request)
182206

@@ -243,7 +267,9 @@ async def _handle_workflow_agent(
243267
The sandbox may be deactivated after some period of inactivity, and only data managed
244268
by the hosting infrastructure or files will be preserved upon deactivation.
245269
"""
246-
input_text = await context.get_input_text()
270+
input_items = await context.get_input_items()
271+
input_messages = _items_to_messages(input_items)
272+
247273
is_streaming_request = self._is_streaming_request(request)
248274

249275
_, are_options_set = _to_chat_options(request)
@@ -296,7 +322,7 @@ async def _handle_workflow_agent(
296322

297323
if not is_streaming_request:
298324
# Run the agent in non-streaming mode
299-
response = await self._agent.run(input_text, stream=False, checkpoint_storage=checkpoint_storage)
325+
response = await self._agent.run(input_messages, stream=False, checkpoint_storage=checkpoint_storage)
300326

301327
for message in response.messages:
302328
for content in message.contents:
@@ -308,7 +334,7 @@ async def _handle_workflow_agent(
308334
return
309335

310336
# Run the agent in streaming mode
311-
response_stream = self._agent.run(input_text, stream=True, checkpoint_storage=checkpoint_storage)
337+
response_stream = self._agent.run(input_messages, stream=True, checkpoint_storage=checkpoint_storage)
312338

313339
# Track the current active output item builder for streaming;
314340
# lazily created on matching content, closed when a different type arrives.
@@ -532,7 +558,260 @@ def _to_chat_options(request: CreateResponse) -> tuple[ChatOptions, bool]:
532558
# region Input Message Conversion
533559

534560

535-
def _to_messages(history: Sequence[OutputItem]) -> list[Message]:
561+
def _items_to_messages(input_items: Sequence[Item]) -> list[Message]:
562+
"""Converts a sequence of input items to a list of Messages, one per item.
563+
564+
Args:
565+
input_items: The input items to convert.
566+
567+
Returns:
568+
A list of Messages, one per supported input item.
569+
"""
570+
messages: list[Message] = []
571+
for item in input_items:
572+
messages.append(_item_to_message(item))
573+
return messages
574+
575+
576+
def _item_to_message(item: Item) -> Message:
577+
"""Converts an Item to a Message.
578+
579+
Args:
580+
item: The Item to convert.
581+
582+
Returns:
583+
The converted Message.
584+
585+
Raises:
586+
ValueError: If the Item type is not supported.
587+
"""
588+
if item.type == "message":
589+
msg = cast(ItemMessage, item)
590+
if isinstance(msg.content, str):
591+
return Message(role=msg.role, contents=[Content.from_text(msg.content)])
592+
return Message(role=msg.role, contents=[_convert_message_content(part) for part in msg.content])
593+
594+
if item.type == "output_message":
595+
output_msg = cast(ItemOutputMessage, item)
596+
return Message(
597+
role=output_msg.role, contents=[_convert_output_message_content(part) for part in output_msg.content]
598+
)
599+
600+
if item.type == "function_call":
601+
fc = cast(ItemFunctionToolCall, item)
602+
return Message(
603+
role="assistant",
604+
contents=[Content.from_function_call(fc.call_id, fc.name, arguments=fc.arguments)],
605+
)
606+
607+
if item.type == "function_call_output":
608+
fco = cast(FunctionCallOutputItemParam, item)
609+
output = fco.output if isinstance(fco.output, str) else str(fco.output)
610+
return Message(
611+
role="tool",
612+
contents=[Content.from_function_result(fco.call_id, result=output)],
613+
)
614+
615+
if item.type == "reasoning":
616+
reasoning = cast(ItemReasoningItem, item)
617+
reason_contents: list[Content] = []
618+
if reasoning.summary:
619+
for summary in reasoning.summary:
620+
reason_contents.append(Content.from_text(summary.text))
621+
return Message(role="assistant", contents=reason_contents)
622+
623+
if item.type == "mcp_call":
624+
mcp = cast(ItemMcpToolCall, item)
625+
return Message(
626+
role="assistant",
627+
contents=[
628+
Content.from_mcp_server_tool_call(
629+
mcp.id,
630+
mcp.name,
631+
server_name=mcp.server_label,
632+
arguments=mcp.arguments,
633+
)
634+
],
635+
)
636+
637+
if item.type == "mcp_approval_request":
638+
mcp_req = cast(ItemMcpApprovalRequest, item)
639+
mcp_call_content = Content.from_mcp_server_tool_call(
640+
mcp_req.id,
641+
mcp_req.name,
642+
server_name=mcp_req.server_label,
643+
arguments=mcp_req.arguments,
644+
)
645+
return Message(
646+
role="assistant",
647+
contents=[Content.from_function_approval_request(mcp_req.id, mcp_call_content)],
648+
)
649+
650+
if item.type == "mcp_approval_response":
651+
mcp_resp = cast(MCPApprovalResponse, item)
652+
placeholder_content = Content.from_function_call(mcp_resp.approval_request_id, "mcp_approval")
653+
return Message(
654+
role="user",
655+
contents=[
656+
Content.from_function_approval_response(
657+
mcp_resp.approve, mcp_resp.approval_request_id, placeholder_content
658+
)
659+
],
660+
)
661+
662+
if item.type == "code_interpreter_call":
663+
ci = cast(ItemCodeInterpreterToolCall, item)
664+
return Message(
665+
role="assistant",
666+
contents=[Content.from_code_interpreter_tool_call(call_id=ci.id)],
667+
)
668+
669+
if item.type == "image_generation_call":
670+
ig = cast(ItemImageGenToolCall, item)
671+
return Message(
672+
role="assistant",
673+
contents=[Content.from_image_generation_tool_call(image_id=ig.id)],
674+
)
675+
676+
if item.type == "shell_call":
677+
sc = cast(FunctionShellCallItemParam, item)
678+
return Message(
679+
role="assistant",
680+
contents=[
681+
Content.from_shell_tool_call(
682+
call_id=sc.call_id,
683+
commands=sc.action.commands,
684+
status=str(sc.status),
685+
)
686+
],
687+
)
688+
689+
if item.type == "shell_call_output":
690+
sco = cast(FunctionShellCallOutputItemParam, item)
691+
outputs = [
692+
Content.from_shell_command_output(
693+
stdout=out.stdout or "",
694+
stderr=out.stderr or "",
695+
exit_code=getattr(out.outcome, "exit_code", None) if hasattr(out, "outcome") else None,
696+
)
697+
for out in (sco.output or [])
698+
]
699+
return Message(
700+
role="tool",
701+
contents=[
702+
Content.from_shell_tool_result(
703+
call_id=sco.call_id,
704+
outputs=outputs,
705+
max_output_length=sco.max_output_length,
706+
)
707+
],
708+
)
709+
710+
if item.type == "local_shell_call":
711+
lsc = cast(ItemLocalShellToolCall, item)
712+
commands = lsc.action.command if hasattr(lsc.action, "command") and lsc.action.command else []
713+
return Message(
714+
role="assistant",
715+
contents=[
716+
Content.from_shell_tool_call(
717+
call_id=lsc.call_id,
718+
commands=commands,
719+
status=str(lsc.status),
720+
)
721+
],
722+
)
723+
724+
if item.type == "local_shell_call_output":
725+
lsco = cast(ItemLocalShellToolCallOutput, item)
726+
return Message(
727+
role="tool",
728+
contents=[
729+
Content.from_shell_tool_result(
730+
call_id=lsco.id,
731+
outputs=[Content.from_shell_command_output(stdout=lsco.output)],
732+
)
733+
],
734+
)
735+
736+
if item.type == "file_search_call":
737+
fs = cast(ItemFileSearchToolCall, item)
738+
return Message(
739+
role="assistant",
740+
contents=[
741+
Content.from_function_call(
742+
fs.id,
743+
"file_search",
744+
arguments=json.dumps({"queries": fs.queries}),
745+
)
746+
],
747+
)
748+
749+
if item.type == "web_search_call":
750+
ws = cast(ItemWebSearchToolCall, item)
751+
return Message(
752+
role="assistant",
753+
contents=[Content.from_function_call(ws.id, "web_search")],
754+
)
755+
756+
if item.type == "computer_call":
757+
cc = cast(ItemComputerToolCall, item)
758+
return Message(
759+
role="assistant",
760+
contents=[
761+
Content.from_function_call(
762+
cc.call_id,
763+
"computer_use",
764+
arguments=str(cc.action),
765+
)
766+
],
767+
)
768+
769+
if item.type == "computer_call_output":
770+
cco = cast(ComputerCallOutputItemParam, item)
771+
return Message(
772+
role="tool",
773+
contents=[Content.from_function_result(cco.call_id, result=str(cco.output))],
774+
)
775+
776+
if item.type == "custom_tool_call":
777+
ct = cast(ItemCustomToolCall, item)
778+
return Message(
779+
role="assistant",
780+
contents=[Content.from_function_call(ct.call_id, ct.name, arguments=ct.input)],
781+
)
782+
783+
if item.type == "custom_tool_call_output":
784+
cto = cast(ItemCustomToolCallOutput, item)
785+
output = cto.output if isinstance(cto.output, str) else str(cto.output)
786+
return Message(
787+
role="tool",
788+
contents=[Content.from_function_result(cto.call_id, result=output)],
789+
)
790+
791+
if item.type == "apply_patch_call":
792+
ap = cast(ApplyPatchToolCallItemParam, item)
793+
return Message(
794+
role="assistant",
795+
contents=[
796+
Content.from_function_call(
797+
ap.call_id,
798+
"apply_patch",
799+
arguments=str(ap.operation),
800+
)
801+
],
802+
)
803+
804+
if item.type == "apply_patch_call_output":
805+
apo = cast(ApplyPatchToolCallOutputItemParam, item)
806+
return Message(
807+
role="tool",
808+
contents=[Content.from_function_result(apo.call_id, result=apo.output or "")],
809+
)
810+
811+
raise ValueError(f"Unsupported Item type: {item.type}")
812+
813+
814+
def _output_items_to_messages(history: Sequence[OutputItem]) -> list[Message]:
536815
"""Converts a sequence of OutputItem objects to a list of Message objects.
537816
538817
Args:
@@ -543,11 +822,11 @@ def _to_messages(history: Sequence[OutputItem]) -> list[Message]:
543822
"""
544823
messages: list[Message] = []
545824
for item in history:
546-
messages.append(_to_message(item))
825+
messages.append(_output_item_to_message(item))
547826
return messages
548827

549828

550-
def _to_message(item: OutputItem) -> Message:
829+
def _output_item_to_message(item: OutputItem) -> Message:
551830
"""Converts an OutputItem to a Message.
552831
553832
Args:

python/packages/foundry_hosting/pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description = "Foundry Hosting integration for Microsoft Agent Framework."
44
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
55
readme = "README.md"
66
requires-python = ">=3.10"
7-
version = "1.0.0a260423"
7+
version = "1.0.0a260424"
88
license-files = ["LICENSE"]
99
urls.homepage = "https://aka.ms/agent-framework"
1010
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
@@ -24,9 +24,9 @@ classifiers = [
2424
]
2525
dependencies = [
2626
"agent-framework-core>=1.1.1,<2",
27-
"azure-ai-agentserver-core==2.0.0b2",
28-
"azure-ai-agentserver-responses==1.0.0b4",
29-
"azure-ai-agentserver-invocations==1.0.0b2",
27+
"azure-ai-agentserver-core==2.0.0b3",
28+
"azure-ai-agentserver-responses==1.0.0b5",
29+
"azure-ai-agentserver-invocations==1.0.0b3",
3030
]
3131

3232
[tool.uv]

0 commit comments

Comments
 (0)