Reject confirm_code_execution for local code tools on Anthropic path
The Anthropic /v1/messages server-tool path maps a Studio tool alias such as
{"type":"python"} to the local tool loop and runs python/terminal on the
host. That path does not wire the confirmation prompt into its SSE translation
(which is why confirm_tool_calls is already rejected there), so treating
confirm_code_execution as ignored let local code run without the prompt the
flag promises.
Reject confirm_code_execution on this path when a local code-execution tool is
actually selected, mirroring the confirm_tool_calls rejection. The check sits
inside the server-tool branch and is gated on the resolved tool list, so a
non-code request (e.g. web_search) is unaffected and a disabled request
(enable_tools=false / --disable-tools) never reaches it. bypass_permissions
still suppresses the gate.
Update the field docs and split the Anthropic test into a code-tool rejection
case and a non-code ignored case.
This commit is contained in:
parent
718202c996
commit
13c012a7b3
3 changed files with 61 additions and 19 deletions
|
|
@ -775,7 +775,7 @@ class ChatCompletionRequest(BaseModel):
|
|||
)
|
||||
confirm_code_execution: Optional[bool] = Field(
|
||||
None,
|
||||
description = "[x-unsloth] When true, pause only before local code-execution tool calls (python/terminal) and wait for the user to allow/deny each via POST /api/inference/tool-confirm; other tools (web_search, render_html, ...) still run without a prompt. Applies to local python/terminal execution only, so it is ignored for external providers and Anthropic server tools (their code runs in the provider's sandbox, not locally). Independent of confirm_tool_calls; requires stream=true when a local code-execution tool is enabled; bypass_permissions still takes precedence.",
|
||||
description = "[x-unsloth] When true, pause only before local code-execution tool calls (python/terminal) and wait for the user to allow/deny each via POST /api/inference/tool-confirm; other tools (web_search, render_html, ...) still run without a prompt. Supported on the OpenAI-compatible local endpoints (/v1/chat/completions, /v1/responses). It is ignored for external providers (their hosted code runs in the provider's sandbox, not locally); on Anthropic /v1/messages it is rejected when a local code-execution tool is selected, since that path does not wire the confirmation prompt. Independent of confirm_tool_calls; requires stream=true when a local code-execution tool is enabled; bypass_permissions still takes precedence.",
|
||||
)
|
||||
bypass_permissions: Optional[bool] = Field(
|
||||
False,
|
||||
|
|
|
|||
|
|
@ -10227,9 +10227,10 @@ async def anthropic_messages(
|
|||
),
|
||||
)
|
||||
|
||||
# confirm_code_execution guards only local python/terminal; Anthropic server
|
||||
# tools (incl. hosted code execution) run server-side, so the flag does not
|
||||
# apply here and is ignored (documented on the field).
|
||||
# confirm_code_execution is handled below inside the server-tool branch (it is
|
||||
# rejected when a local python/terminal tool is actually selected, mirroring
|
||||
# confirm_tool_calls), so nothing to do pre-switch here: a non-code request is
|
||||
# unaffected and a disabled request never enters that branch.
|
||||
|
||||
# require_vision rejects a swap to a text-only target before it runs, so an
|
||||
# image request can't evict the resident vision model only to hit the vision
|
||||
|
|
@ -10431,6 +10432,36 @@ async def anthropic_messages(
|
|||
payload.enabled_tools,
|
||||
)
|
||||
|
||||
# confirm_code_execution guards local python/terminal execution. On this
|
||||
# path a Studio tool alias like {"type":"python"} maps to the local tool
|
||||
# loop and runs code on this host -- but the Anthropic Messages SSE
|
||||
# translation does not wire the confirmation prompt (which is why
|
||||
# confirm_tool_calls is rejected above). Silently ignoring the flag would
|
||||
# run python/terminal without the promised prompt, so reject it when a
|
||||
# local code-execution tool is actually selected. A non-code selection
|
||||
# (e.g. web_search) is unaffected, and bypass_permissions suppresses the
|
||||
# gate. Gated on server_tools above, so a disabled request never reaches
|
||||
# here.
|
||||
if (
|
||||
bool(getattr(payload, "confirm_code_execution", False))
|
||||
and not bool(getattr(payload, "bypass_permissions", False))
|
||||
and _enables_code_execution_tool(openai_tools)
|
||||
):
|
||||
api_monitor.fail(
|
||||
monitor_id,
|
||||
"confirm_code_execution is not supported for Anthropic Messages server tools.",
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code = 400,
|
||||
detail = anthropic_error_body(
|
||||
"confirm_code_execution is not supported for Anthropic Messages "
|
||||
"server tools; it only guards local python/terminal execution on "
|
||||
"the OpenAI-compatible endpoints (/v1/chat/completions).",
|
||||
status = 400,
|
||||
err_type = "invalid_request_error",
|
||||
),
|
||||
)
|
||||
|
||||
# Build tool-use system prompt nudge (same logic as /chat/completions)
|
||||
_nudge = _build_tool_action_nudge(
|
||||
tools = openai_tools,
|
||||
|
|
|
|||
|
|
@ -1718,23 +1718,34 @@ class TestAnthropicMessagesToolRouting:
|
|||
assert "confirm_tool_calls is not supported" in exc.value.detail["error"]["message"]
|
||||
assert backend.calls == []
|
||||
|
||||
def test_confirm_code_execution_ignored_for_server_tools(self, monkeypatch):
|
||||
# confirm_code_execution guards only local python/terminal execution.
|
||||
# Anthropic server tools run in the provider's sandbox, not locally, so
|
||||
# the flag does not apply here: it is ignored (never rejects), whether
|
||||
# the requested server tool is code execution or not.
|
||||
for tool in (
|
||||
{"type": "python", "name": "python"},
|
||||
{"type": "web_search_20250305", "name": "web_search"},
|
||||
):
|
||||
backend = _mock_backend(monkeypatch)
|
||||
payload = _basic_payload(
|
||||
confirm_code_execution = True,
|
||||
tools = [tool],
|
||||
)
|
||||
def test_confirm_code_execution_rejected_for_code_server_tools(self, monkeypatch):
|
||||
# A Studio {"type":"python"} alias runs the local python executor via the
|
||||
# tool loop, but this path does not wire the confirmation prompt (like
|
||||
# confirm_tool_calls above). Ignoring the flag would run code without the
|
||||
# prompt, so it is rejected when a local code-execution tool is selected.
|
||||
backend = _mock_backend(monkeypatch)
|
||||
payload = _basic_payload(
|
||||
confirm_code_execution = True,
|
||||
tools = [{"type": "python", "name": "python"}],
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
_drive(anthropic_messages(payload, request = None, current_subject = "t"))
|
||||
assert backend.calls[0][0] == "tools"
|
||||
assert exc.value.status_code == 400
|
||||
assert "confirm_code_execution is not supported" in exc.value.detail["error"]["message"]
|
||||
assert backend.calls == []
|
||||
|
||||
def test_confirm_code_execution_ignored_for_non_code_server_tools(self, monkeypatch):
|
||||
# web_search is not code execution, so the flag does not apply and the
|
||||
# request proceeds normally rather than being rejected.
|
||||
backend = _mock_backend(monkeypatch)
|
||||
payload = _basic_payload(
|
||||
confirm_code_execution = True,
|
||||
tools = [{"type": "web_search_20250305", "name": "web_search"}],
|
||||
)
|
||||
|
||||
_drive(anthropic_messages(payload, request = None, current_subject = "t"))
|
||||
assert backend.calls[0][0] == "tools"
|
||||
|
||||
def test_per_request_enable_tools_false_blocks_server_tool_alias(self, monkeypatch):
|
||||
backend = _mock_backend(monkeypatch)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue