agent-framework-foundry 1.2.2__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-framework-foundry
3
- Version: 1.2.2
3
+ Version: 1.4.0
4
4
  Summary: Microsoft Foundry integrations for Microsoft Agent Framework.
5
5
  Author-email: Microsoft <af-support@microsoft.com>
6
6
  Requires-Python: >=3.10
@@ -16,8 +16,8 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Programming Language :: Python :: 3.14
17
17
  Classifier: Typing :: Typed
18
18
  License-File: LICENSE
19
- Requires-Dist: agent-framework-core>=1.2.2,<2
20
- Requires-Dist: agent-framework-openai>=1.2.2,<2
19
+ Requires-Dist: agent-framework-core>=1.4.0,<2
20
+ Requires-Dist: agent-framework-openai>=1.4.0,<2
21
21
  Requires-Dist: azure-ai-inference>=1.0.0b9,<1.0.0b10
22
22
  Requires-Dist: azure-ai-projects>=2.1.0,<3.0
23
23
  Project-URL: homepage, https://aka.ms/agent-framework
@@ -48,32 +48,7 @@ For hosted `FoundryAgent`, the toolbox must already be attached to the agent in
48
48
 
49
49
  ### Using toolboxes with `FoundryChatClient`
50
50
 
51
- There are two patterns for wiring a toolbox into a `FoundryChatClient`-backed agent.
52
-
53
- **1. Fetch, optionally filter, and pass the tools directly**
54
-
55
- Load the toolbox from the Microsoft Foundry project, optionally select a subset of its tools, and hand them to an `Agent` alongside any other tools you own:
56
-
57
- ```python
58
- from agent_framework import Agent
59
- from agent_framework.foundry import FoundryChatClient, select_toolbox_tools
60
-
61
- client = FoundryChatClient(...)
62
- toolbox = await client.get_toolbox("my-toolbox", version="3")
63
-
64
- # Pass the whole toolbox:
65
- agent = Agent(client=client, tools=toolbox)
66
-
67
- # Or filter to a subset first:
68
- selected = select_toolbox_tools(toolbox, include_types=["code_interpreter", "mcp"])
69
- agent = Agent(client=client, tools=selected)
70
- ```
71
-
72
- See [`foundry_chat_client_with_toolbox.py`](../../samples/02-agents/providers/foundry/foundry_chat_client_with_toolbox.py) for a full example, including combining multiple toolboxes.
73
-
74
- **2. Connect to the toolbox's MCP endpoint with `MCPStreamableHTTPTool`**
75
-
76
- Each toolbox is reachable as an MCP server. Instead of fetching and fanning out its individual tool definitions, you can point a MAF `MCPStreamableHTTPTool` at the toolbox's MCP endpoint — the agent then discovers and calls its tools over MCP at runtime:
51
+ Each toolbox is reachable as an MCP server. Connect to the toolbox's MCP endpoint with `MCPStreamableHTTPTool` — the agent then discovers and calls its tools over MCP at runtime:
77
52
 
78
53
  ```python
79
54
  from agent_framework import Agent, MCPStreamableHTTPTool
@@ -21,32 +21,7 @@ For hosted `FoundryAgent`, the toolbox must already be attached to the agent in
21
21
 
22
22
  ### Using toolboxes with `FoundryChatClient`
23
23
 
24
- There are two patterns for wiring a toolbox into a `FoundryChatClient`-backed agent.
25
-
26
- **1. Fetch, optionally filter, and pass the tools directly**
27
-
28
- Load the toolbox from the Microsoft Foundry project, optionally select a subset of its tools, and hand them to an `Agent` alongside any other tools you own:
29
-
30
- ```python
31
- from agent_framework import Agent
32
- from agent_framework.foundry import FoundryChatClient, select_toolbox_tools
33
-
34
- client = FoundryChatClient(...)
35
- toolbox = await client.get_toolbox("my-toolbox", version="3")
36
-
37
- # Pass the whole toolbox:
38
- agent = Agent(client=client, tools=toolbox)
39
-
40
- # Or filter to a subset first:
41
- selected = select_toolbox_tools(toolbox, include_types=["code_interpreter", "mcp"])
42
- agent = Agent(client=client, tools=selected)
43
- ```
44
-
45
- See [`foundry_chat_client_with_toolbox.py`](../../samples/02-agents/providers/foundry/foundry_chat_client_with_toolbox.py) for a full example, including combining multiple toolboxes.
46
-
47
- **2. Connect to the toolbox's MCP endpoint with `MCPStreamableHTTPTool`**
48
-
49
- Each toolbox is reachable as an MCP server. Instead of fetching and fanning out its individual tool definitions, you can point a MAF `MCPStreamableHTTPTool` at the toolbox's MCP endpoint — the agent then discovers and calls its tools over MCP at runtime:
24
+ Each toolbox is reachable as an MCP server. Connect to the toolbox's MCP endpoint with `MCPStreamableHTTPTool` — the agent then discovers and calls its tools over MCP at runtime:
50
25
 
51
26
  ```python
52
27
  from agent_framework import Agent, MCPStreamableHTTPTool
@@ -16,7 +16,6 @@ from ._foundry_evals import (
16
16
  evaluate_traces,
17
17
  )
18
18
  from ._memory_provider import FoundryMemoryProvider
19
- from ._tools import FoundryHostedToolType, get_toolbox_tool_name, get_toolbox_tool_type, select_toolbox_tools
20
19
 
21
20
  try:
22
21
  __version__ = importlib.metadata.version(__name__)
@@ -32,7 +31,6 @@ __all__ = [
32
31
  "FoundryEmbeddingOptions",
33
32
  "FoundryEmbeddingSettings",
34
33
  "FoundryEvals",
35
- "FoundryHostedToolType",
36
34
  "FoundryMemoryProvider",
37
35
  "RawFoundryAgent",
38
36
  "RawFoundryAgentChatClient",
@@ -41,7 +39,4 @@ __all__ = [
41
39
  "__version__",
42
40
  "evaluate_foundry_target",
43
41
  "evaluate_traces",
44
- "get_toolbox_tool_name",
45
- "get_toolbox_tool_type",
46
- "select_toolbox_tools",
47
42
  ]
@@ -135,6 +135,18 @@ def _uses_foundry_agent_session(conversation_id: Any) -> bool:
135
135
  )
136
136
 
137
137
 
138
+ def _build_agent_reference(agent_name: str, agent_version: str | None) -> dict[str, str]:
139
+ """Build the Responses API ``agent_reference`` payload for non-preview Foundry agent calls.
140
+
141
+ Used for both Prompt Agents and HostedAgents on the ``allow_preview=False`` code path —
142
+ the preview branch instead injects identity via ``project_client.get_openai_client(agent_name=...)``.
143
+ """
144
+ ref: dict[str, str] = {"name": agent_name, "type": "agent_reference"}
145
+ if agent_version:
146
+ ref["version"] = agent_version
147
+ return ref
148
+
149
+
138
150
  class RawFoundryAgentChatClient( # type: ignore[misc]
139
151
  RawOpenAIChatClient[FoundryAgentOptionsT],
140
152
  Generic[FoundryAgentOptionsT],
@@ -342,6 +354,12 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
342
354
  run_options.pop("previous_response_id", None)
343
355
  run_options.pop("conversation", None)
344
356
  extra_body["agent_session_id"] = conversation_id
357
+ # Non-preview Prompt/Hosted Agent calls need agent_reference in the request body to
358
+ # tell the Responses API which Foundry agent (and version) is in use, since ``model``
359
+ # is stripped below. The preview path injects the reference via the OpenAI client kwarg
360
+ # ``agent_name`` instead, so skip there. See issue #5582.
361
+ if not self.allow_preview:
362
+ extra_body.setdefault("agent_reference", _build_agent_reference(self.agent_name, self.agent_version))
345
363
  if extra_body:
346
364
  run_options["extra_body"] = extra_body
347
365
 
@@ -400,12 +418,6 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
400
418
  self,
401
419
  tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
402
420
  ) -> list[Any]:
403
- """Prepare tools for Foundry agent Responses API calls.
404
-
405
- Mirrors ``RawFoundryChatClient`` sanitization so toolbox-fetched MCP
406
- tools with extra read-model fields continue to work through the agent
407
- surface.
408
- """
409
421
  response_tools = super()._prepare_tools_for_openai(tools)
410
422
  return [_sanitize_foundry_response_tool(tool_item) for tool_item in response_tools]
411
423
 
@@ -16,7 +16,6 @@ from agent_framework import (
16
16
  load_settings,
17
17
  )
18
18
  from agent_framework._compaction import CompactionStrategy, TokenizerProtocol
19
- from agent_framework._feature_stage import ExperimentalFeature, experimental
20
19
  from agent_framework._telemetry import get_user_agent
21
20
  from agent_framework.observability import ChatTelemetryLayer
22
21
  from agent_framework_openai._chat_client import OpenAIChatOptions, RawOpenAIChatClient
@@ -36,7 +35,7 @@ from azure.core.credentials_async import AsyncTokenCredential
36
35
 
37
36
  from agent_framework_foundry._oauth_helpers import try_parse_oauth_consent_event
38
37
 
39
- from ._tools import _sanitize_foundry_response_tool, fetch_toolbox # pyright: ignore[reportPrivateUsage]
38
+ from ._tools import _sanitize_foundry_response_tool # pyright: ignore[reportPrivateUsage]
40
39
 
41
40
  if sys.version_info >= (3, 13):
42
41
  from typing import TypeVar # type: ignore # pragma: no cover
@@ -53,7 +52,6 @@ else:
53
52
 
54
53
  if TYPE_CHECKING:
55
54
  from agent_framework import ChatAndFunctionMiddlewareTypes, ToolTypes
56
- from azure.ai.projects.models import ToolboxVersionObject
57
55
 
58
56
  logger: logging.Logger = logging.getLogger("agent_framework.foundry")
59
57
 
@@ -234,13 +232,6 @@ class RawFoundryChatClient( # type: ignore[misc]
234
232
  self,
235
233
  tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
236
234
  ) -> list[Any]:
237
- """Prepare tools for Foundry Responses API calls.
238
-
239
- Foundry toolbox reads can surface MCP tool objects with extra fields
240
- (for example ``name``) that are accepted by the toolbox API but rejected
241
- by the Responses API. Sanitize those hosted-tool payloads before sending
242
- them downstream.
243
- """
244
235
  response_tools = super()._prepare_tools_for_openai(tools)
245
236
  return [_sanitize_foundry_response_tool(tool_item) for tool_item in response_tools]
246
237
 
@@ -510,37 +501,6 @@ class RawFoundryChatClient( # type: ignore[misc]
510
501
 
511
502
  # endregion
512
503
 
513
- # region Toolbox methods (instance methods — these hit the network)
514
-
515
- @experimental(feature_id=ExperimentalFeature.TOOLBOXES)
516
- async def get_toolbox(
517
- self,
518
- name: str,
519
- *,
520
- version: str | None = None,
521
- ) -> ToolboxVersionObject:
522
- """Fetch a Foundry toolbox by name.
523
-
524
- If ``version`` is omitted, resolves the toolbox's current default version
525
- (two requests). If ``version`` is specified, fetches that version directly
526
- (single request).
527
-
528
- Args:
529
- name: The name of the toolbox.
530
-
531
- Keyword Args:
532
- version: Optional immutable version identifier to pin to.
533
-
534
- Returns:
535
- A ``ToolboxVersionObject``. Pass its ``tools`` attribute to
536
- ``Agent(tools=toolbox.tools)``.
537
-
538
- Raises:
539
- azure.core.exceptions.ResourceNotFoundError: If the toolbox or
540
- the requested version does not exist.
541
- """
542
- return await fetch_toolbox(self.project_client, name, version)
543
-
544
504
 
545
505
  class FoundryChatClient( # type: ignore[misc]
546
506
  FunctionInvocationLayer[FoundryChatOptionsT],
@@ -0,0 +1,72 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """Shared tool helpers for Foundry chat clients.
4
+
5
+ Includes Responses-API payload sanitization for Foundry hosted tools.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Mapping
11
+ from typing import Any, cast
12
+
13
+ from azure.ai.projects.models import MCPTool as FoundryMCPTool
14
+
15
+
16
+ def _validate_hosted_tool_payload(sanitized: Mapping[str, Any]) -> None:
17
+ """Fail fast on hosted tool payloads that would always be rejected by the Responses API.
18
+
19
+ These mismatches are not injectable defaults — the caller must supply the
20
+ missing information — so surfacing a clear error here points at the tool
21
+ definition instead of letting the API return a generic 400.
22
+ """
23
+ tool_type = sanitized.get("type")
24
+ if tool_type == "file_search" and not sanitized.get("vector_store_ids"):
25
+ raise ValueError(
26
+ "'file_search' tool is missing required 'vector_store_ids'. "
27
+ "Update the tool definition to include at least one vector store ID."
28
+ )
29
+ if tool_type == "mcp" and not sanitized.get("server_url") and not sanitized.get("project_connection_id"):
30
+ raise ValueError(
31
+ "'mcp' tool is missing both 'server_url' and 'project_connection_id'. "
32
+ "Update the tool definition to include one of these."
33
+ )
34
+
35
+
36
+ def _sanitize_foundry_response_tool(tool_item: Any) -> Any: # pyright: ignore[reportUnusedFunction]
37
+ """Return a Responses-API-safe tool payload for Foundry hosted tools.
38
+
39
+ Reconciles known mismatches between hosted tool definitions and the Responses API:
40
+
41
+ 1. Hosted tool objects may carry read-model fields such as top-level ``name``
42
+ and ``description``. The Responses API rejects at least ``name`` with
43
+ ``Unknown parameter: 'tools[0].name'``. These fields are stripped from
44
+ non-function hosted tool payloads.
45
+ 2. ``code_interpreter`` tools without a ``container`` field (the Azure SDK
46
+ treats it as optional) are rejected by the Responses API with
47
+ ``Missing required parameter: 'tools[N].container'``. A default
48
+ ``{"type": "auto"}`` container is injected when absent.
49
+ 3. Hosted tools that are structurally incomplete in ways that cannot be
50
+ defaulted (``file_search`` without ``vector_store_ids``, ``mcp`` without
51
+ either ``server_url`` or ``project_connection_id``) raise ``ValueError``
52
+ with a message that points at the tool definition.
53
+ """
54
+ if isinstance(tool_item, FoundryMCPTool):
55
+ sanitized: dict[str, Any] = dict(cast("Mapping[str, Any]", tool_item))
56
+ sanitized.pop("name", None)
57
+ sanitized.pop("description", None)
58
+ _validate_hosted_tool_payload(sanitized)
59
+ return sanitized
60
+
61
+ if isinstance(tool_item, Mapping):
62
+ mapping = cast("Mapping[str, Any]", tool_item)
63
+ if "type" in mapping and mapping.get("type") not in {"function", "custom"}:
64
+ sanitized = dict(mapping)
65
+ sanitized.pop("name", None)
66
+ sanitized.pop("description", None)
67
+ if sanitized.get("type") == "code_interpreter" and "container" not in sanitized:
68
+ sanitized["container"] = {"type": "auto"}
69
+ _validate_hosted_tool_payload(sanitized)
70
+ return sanitized
71
+
72
+ return cast(Any, tool_item)
@@ -4,7 +4,7 @@ description = "Microsoft Foundry integrations for Microsoft Agent Framework."
4
4
  authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
- version = "1.2.2"
7
+ version = "1.4.0"
8
8
  license-files = ["LICENSE"]
9
9
  urls.homepage = "https://aka.ms/agent-framework"
10
10
  urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
@@ -23,8 +23,8 @@ classifiers = [
23
23
  "Typing :: Typed",
24
24
  ]
25
25
  dependencies = [
26
- "agent-framework-core>=1.2.2,<2",
27
- "agent-framework-openai>=1.2.2,<2",
26
+ "agent-framework-core>=1.4.0,<2",
27
+ "agent-framework-openai>=1.4.0,<2",
28
28
  "azure-ai-inference>=1.0.0b9,<1.0.0b10",
29
29
  "azure-ai-projects>=2.1.0,<3.0",
30
30
  ]
@@ -1,197 +0,0 @@
1
- # Copyright (c) Microsoft. All rights reserved.
2
-
3
- """Shared tool helpers for Foundry chat clients.
4
-
5
- Includes:
6
-
7
- * *Toolbox* helpers — a *toolbox* is a named, versioned bundle of tool
8
- definitions stored in an Azure AI Foundry project.
9
- * Responses-API payload sanitization for Foundry hosted tools.
10
- """
11
-
12
- from __future__ import annotations
13
-
14
- from collections.abc import Callable, Collection, Mapping, Sequence
15
- from typing import TYPE_CHECKING, Any, Literal, TypeAlias, cast
16
-
17
- from agent_framework._feature_stage import ExperimentalFeature, experimental
18
- from azure.ai.projects.models import MCPTool as FoundryMCPTool
19
-
20
- if TYPE_CHECKING:
21
- from azure.ai.projects.aio import AIProjectClient
22
- from azure.ai.projects.models import Tool, ToolboxVersionObject
23
-
24
- FoundryHostedToolType: TypeAlias = (
25
- Literal[
26
- "code_interpreter",
27
- "file_search",
28
- "image_generation",
29
- "mcp",
30
- "web_search",
31
- ]
32
- | str
33
- )
34
- ToolboxToolSelectionInput: TypeAlias = "ToolboxVersionObject | Sequence[Tool | dict[str, Any]]"
35
-
36
-
37
- @experimental(feature_id=ExperimentalFeature.TOOLBOXES)
38
- async def fetch_toolbox(
39
- project_client: AIProjectClient,
40
- name: str,
41
- version: str | None = None,
42
- ) -> ToolboxVersionObject:
43
- """Fetch a toolbox version via an ``AIProjectClient``.
44
-
45
- If ``version`` is omitted, resolves the toolbox's current default
46
- version (two requests: one to ``.get(name)`` for the default version
47
- pointer, one to ``.get_version(name, version)`` for the tools). If
48
- ``version`` is specified, fetches that version directly (single request).
49
- """
50
- if version is None:
51
- handle = await project_client.beta.toolboxes.get(name)
52
- version = handle.default_version
53
- return await project_client.beta.toolboxes.get_version(name, version)
54
-
55
-
56
- @experimental(feature_id=ExperimentalFeature.TOOLBOXES)
57
- def get_toolbox_tool_name(tool: Tool | dict[str, Any]) -> str | None:
58
- """Return the best-effort display/selection name for a toolbox tool.
59
-
60
- Selection precedence:
61
- 1. MCP ``server_label``
62
- 2. Generic tool ``name``
63
- 3. Tool ``type``
64
- """
65
- if isinstance(tool, dict):
66
- if server_label := tool.get("server_label"):
67
- return str(server_label)
68
- if name := tool.get("name"):
69
- return str(name)
70
- if tool_type := tool.get("type"):
71
- return str(tool_type)
72
- return None
73
-
74
- if server_label := getattr(tool, "server_label", None):
75
- return str(server_label)
76
- if name := getattr(tool, "name", None):
77
- return str(name)
78
- if tool_type := getattr(tool, "type", None):
79
- return str(tool_type)
80
- return None
81
-
82
-
83
- @experimental(feature_id=ExperimentalFeature.TOOLBOXES)
84
- def get_toolbox_tool_type(tool: Tool | dict[str, Any]) -> str | None:
85
- """Return the raw tool ``type`` if present."""
86
- tool_type = tool.get("type") if isinstance(tool, dict) else getattr(tool, "type", None)
87
- return str(tool_type) if tool_type is not None else None
88
-
89
-
90
- @experimental(feature_id=ExperimentalFeature.TOOLBOXES)
91
- def select_toolbox_tools(
92
- tools: ToolboxToolSelectionInput,
93
- *,
94
- include_names: Collection[str] | None = None,
95
- exclude_names: Collection[str] | None = None,
96
- include_types: Collection[FoundryHostedToolType] | None = None,
97
- exclude_types: Collection[FoundryHostedToolType] | None = None,
98
- predicate: Callable[[Tool | dict[str, Any]], bool] | None = None,
99
- ) -> list[Tool | dict[str, Any]]:
100
- """Filter toolbox tools by normalized name, raw type, and/or predicate.
101
-
102
- Normalized name precedence:
103
- 1. ``server_label`` for MCP tools
104
- 2. ``name``
105
- 3. ``type``
106
- """
107
- tool_items: Sequence[Tool | dict[str, Any]] = (
108
- tools if isinstance(tools, Sequence) else cast("Sequence[Tool | dict[str, Any]]", tools.tools)
109
- )
110
- include_name_set = {str(item) for item in include_names} if include_names is not None else None
111
- exclude_name_set = {str(item) for item in exclude_names} if exclude_names is not None else None
112
- include_type_set = {str(item) for item in include_types} if include_types is not None else None
113
- exclude_type_set = {str(item) for item in exclude_types} if exclude_types is not None else None
114
-
115
- selected: list[Tool | dict[str, Any]] = []
116
- for tool in tool_items:
117
- tool_name = get_toolbox_tool_name(tool)
118
- tool_type = get_toolbox_tool_type(tool)
119
-
120
- if include_name_set is not None and tool_name not in include_name_set:
121
- continue
122
- if exclude_name_set is not None and tool_name in exclude_name_set:
123
- continue
124
- if include_type_set is not None and tool_type not in include_type_set:
125
- continue
126
- if exclude_type_set is not None and tool_type in exclude_type_set:
127
- continue
128
- if predicate is not None and not predicate(tool):
129
- continue
130
-
131
- selected.append(tool)
132
-
133
- return selected
134
-
135
-
136
- def _validate_hosted_tool_payload(sanitized: Mapping[str, Any]) -> None:
137
- """Fail fast on hosted tool payloads that would always be rejected by the Responses API.
138
-
139
- These mismatches are not injectable defaults — the caller must supply the
140
- missing information — so surfacing a clear error here points at the toolbox
141
- definition instead of letting the API return a generic 400.
142
- """
143
- tool_type = sanitized.get("type")
144
- if tool_type == "file_search" and not sanitized.get("vector_store_ids"):
145
- raise ValueError(
146
- "'file_search' tool is missing required 'vector_store_ids'. "
147
- "If this came from a Foundry toolbox, update the toolbox definition "
148
- "to include at least one vector store ID."
149
- )
150
- if tool_type == "mcp" and not sanitized.get("server_url") and not sanitized.get("project_connection_id"):
151
- raise ValueError(
152
- "'mcp' tool is missing both 'server_url' and 'project_connection_id'. "
153
- "If this came from a Foundry toolbox, update the toolbox definition "
154
- "to include one of these."
155
- )
156
-
157
-
158
- def _sanitize_foundry_response_tool(tool_item: Any) -> Any: # pyright: ignore[reportUnusedFunction]
159
- """Return a Responses-API-safe tool payload for Foundry hosted tools.
160
-
161
- Reconciles known mismatches between toolbox reads and the Responses API:
162
-
163
- 1. Toolbox reads can return hosted tool objects decorated with read-model
164
- fields such as top-level ``name`` and ``description``. The Responses API
165
- rejects at least ``name`` with ``Unknown parameter: 'tools[0].name'``.
166
- These fields are stripped from non-function hosted tool payloads.
167
- 2. ``code_interpreter`` tools stored in a toolbox without a ``container``
168
- field (the Azure SDK treats it as optional) are rejected by the Responses
169
- API with ``Missing required parameter: 'tools[N].container'``. A default
170
- ``{"type": "auto"}`` container is injected when absent.
171
- 3. Hosted tools that are structurally incomplete in ways that cannot be
172
- defaulted (``file_search`` without ``vector_store_ids``, ``mcp`` without
173
- either ``server_url`` or ``project_connection_id``) raise ``ValueError``
174
- with a message that points at the toolbox definition.
175
-
176
- These are workarounds until the toolbox/Responses proxy normalizes payloads
177
- server-side.
178
- """
179
- if isinstance(tool_item, FoundryMCPTool):
180
- sanitized: dict[str, Any] = dict(cast("Mapping[str, Any]", tool_item))
181
- sanitized.pop("name", None)
182
- sanitized.pop("description", None)
183
- _validate_hosted_tool_payload(sanitized)
184
- return sanitized
185
-
186
- if isinstance(tool_item, Mapping):
187
- mapping = cast("Mapping[str, Any]", tool_item)
188
- if "type" in mapping and mapping.get("type") not in {"function", "custom"}:
189
- sanitized = dict(mapping)
190
- sanitized.pop("name", None)
191
- sanitized.pop("description", None)
192
- if sanitized.get("type") == "code_interpreter" and "container" not in sanitized:
193
- sanitized["container"] = {"type": "auto"}
194
- _validate_hosted_tool_payload(sanitized)
195
- return sanitized
196
-
197
- return cast(Any, tool_item)