refactor(agent): build_api_kwargs — shared chat kwargs for profile/legacy paths; xAI tool_search alias rewrite helper

This commit is contained in:
Teknium
2026-09-02 12:49:44 -07:00
parent c5c6d7e566
commit 89a5ce12dd
+66 -96
View File
@@ -2000,6 +2000,49 @@ def _reasoning_config_for_wire(agent):
return agent.reasoning_config
def _alias_tool_search_bridge_for_xai(agent, transport, tools_for_api):
"""xAI chat-completions reserves the function name ``tool_search`` for its
native tool and 400s when the client bridge declares it (#95003) — same
reserved-name class the codex branch sanitizes (#27197). Rename the wire
declaration to an alias; ``normalize_response`` maps calls back via the
transport's request-local ``_last_wire_aliases`` provenance, which is
reset here for THIS request so a stale map from an earlier request can't
reverse-map a name this one never aliased. Deep-copy first (#27907):
tools_for_api aliases agent.tools, so an in-place rename would corrupt
the shared registry for every later non-xAI request."""
if transport is not None and hasattr(transport, "_last_wire_aliases"):
transport._last_wire_aliases = {}
is_xai_chat = (
agent.provider in {"xai", "xai-oauth"}
or agent._base_url_hostname == "api.x.ai"
)
if not (is_xai_chat and tools_for_api):
return tools_for_api
try:
import copy as _copy_xai
from agent.transports.chat_completions import (
_rename_tool_search_bridge_for_xai,
)
has_bridge = any(
(t.get("function") or {}).get("name") == "tool_search"
for t in tools_for_api
if isinstance(t, dict)
)
if has_bridge:
tools_for_api = _copy_xai.deepcopy(tools_for_api)
tools_for_api, alias_map = _rename_tool_search_bridge_for_xai(tools_for_api)
if transport is not None:
transport._last_wire_aliases = alias_map
except Exception as exc:
logger.warning(
"%s⚠️ Failed to alias tool_search bridge for xAI: %s",
getattr(agent, "log_prefix", ""), exc,
)
return tools_for_api
def build_api_kwargs(agent, api_messages: list, tools_for_api: list | None = None) -> dict:
"""Build the keyword arguments dict for the active API mode."""
# One-shot continuation override — consumed exactly once, on the FIRST
@@ -2138,53 +2181,7 @@ def build_api_kwargs(agent, api_messages: list, tools_for_api: list | None = Non
# ── chat_completions (default) ─────────────────────────────────────
_ct = agent._get_transport()
# xAI's chat-completions endpoint reserves the function name
# ``tool_search`` for its native server-side tool and rejects the whole
# request when the client Tool Search bridge declares it (HTTP 400
# "The function name tool_search is reserved for the tool_search tool",
# #95003) — same reserved-name class the codex_responses branch above
# already sanitizes tools for (#27197). Rename the bridge's wire
# declaration to an alias; normalize_response maps model calls back.
# Deep-copy first (the #27907 in-place-mutation lesson): tools_for_api
# aliases agent.tools, and renaming in place would corrupt the shared
# per-agent tool registry for every later non-xAI request.
_is_xai_chat = (
agent.provider in {"xai", "xai-oauth"}
or agent._base_url_hostname == "api.x.ai"
)
# Reset request-local alias provenance for THIS request; the rewrite
# below repopulates it when it actually emits aliases. Without the
# reset, a stale map from an earlier request on the same transport
# could reverse-map a name this request never aliased.
if _ct is not None and hasattr(_ct, "_last_wire_aliases"):
_ct._last_wire_aliases = {}
if _is_xai_chat and tools_for_api:
try:
import copy as _copy_xai
from agent.transports.chat_completions import (
_rename_tool_search_bridge_for_xai,
)
_has_bridge = any(
(t.get("function") or {}).get("name") == "tool_search"
for t in tools_for_api
if isinstance(t, dict)
)
if _has_bridge:
tools_for_api = _copy_xai.deepcopy(tools_for_api)
tools_for_api, _xai_alias_map = _rename_tool_search_bridge_for_xai(
tools_for_api
)
# Record provenance so normalize_response reverses ONLY the
# aliases this request put on the wire.
if _ct is not None:
_ct._last_wire_aliases = _xai_alias_map
except Exception as exc:
logger.warning(
"%s⚠️ Failed to alias tool_search bridge for xAI: %s",
getattr(agent, "log_prefix", ""), exc,
)
tools_for_api = _alias_tool_search_bridge_for_xai(agent, _ct, tools_for_api)
# Provider detection flags
_is_qwen = agent._is_qwen_portal()
@@ -2246,59 +2243,21 @@ def build_api_kwargs(agent, api_messages: list, tools_for_api: list | None = Non
"promptId": str(uuid.uuid4()),
}
# ── Provider profile path (registered providers) ───────────────────
# Profiles handle per-provider quirks via hooks. When a profile is
# found, delegate fully; otherwise fall through to the legacy flag path.
# ── Provider profile path (registered providers) vs legacy flag path ──
try:
from providers import get_provider_profile
_profile = get_provider_profile(agent.provider)
except Exception:
_profile = None
if _profile:
_ephemeral_out = getattr(agent, "_ephemeral_max_output_tokens", None)
if _ephemeral_out is not None:
agent._ephemeral_max_output_tokens = None
# Strip image parts for non-vision models that have provider profiles
# (e.g. DeepSeek, Kimi). The legacy path below already does this, but
# registered providers with profiles were bypassing the strip.
api_messages = agent._prepare_messages_for_non_vision_model(api_messages)
return _ct.build_kwargs(
model=agent.model,
messages=api_messages,
tools=tools_for_api,
base_url=agent.base_url,
timeout=agent._resolved_api_call_timeout(),
max_tokens=agent.max_tokens,
ephemeral_max_output_tokens=_ephemeral_out,
max_tokens_param_fn=agent._max_tokens_param,
reasoning_config=_wire_reasoning_config,
request_overrides=_request_overrides,
session_id=getattr(agent, "session_id", None),
cache_scope_id=_cache_scope_id,
provider_profile=_profile,
ollama_num_ctx=agent._ollama_num_ctx,
# Context forwarded to profile hooks:
provider_preferences=_prefs or None,
openrouter_min_coding_score=agent.openrouter_min_coding_score,
anthropic_max_output=_ant_max,
supports_reasoning=agent._supports_reasoning_extra_body(),
qwen_session_metadata=_qwen_meta,
)
# ── Legacy flag path ────────────────────────────────────────────
# Reached only when get_provider_profile() returns None — i.e. a
# completely unknown provider not in providers/ registry.
# One-shot ephemeral output cap is consumed by whichever path builds the request.
_ephemeral_out = getattr(agent, "_ephemeral_max_output_tokens", None)
if _ephemeral_out is not None:
agent._ephemeral_max_output_tokens = None
# Strip image parts for non-vision models (no-op when vision-capable).
# Strip image parts for non-vision models (no-op when vision-capable) —
# on BOTH paths; registered providers with profiles used to bypass it.
_msgs_for_chat = agent._prepare_messages_for_non_vision_model(api_messages)
return _ct.build_kwargs(
_common = dict(
model=agent.model,
messages=_msgs_for_chat,
tools=tools_for_api,
@@ -2311,6 +2270,23 @@ def build_api_kwargs(agent, api_messages: list, tools_for_api: list | None = Non
request_overrides=_request_overrides,
session_id=getattr(agent, "session_id", None),
cache_scope_id=_cache_scope_id,
ollama_num_ctx=agent._ollama_num_ctx,
provider_preferences=_prefs or None,
openrouter_min_coding_score=agent.openrouter_min_coding_score,
anthropic_max_output=_ant_max,
supports_reasoning=agent._supports_reasoning_extra_body(),
qwen_session_metadata=_qwen_meta,
)
if _profile:
# Profiles handle per-provider quirks via hooks fed the context above.
return _ct.build_kwargs(provider_profile=_profile, **_common)
# ── Legacy flag path ────────────────────────────────────────────
# Reached only when get_provider_profile() returns None — i.e. a
# completely unknown provider not in providers/ registry.
return _ct.build_kwargs(
**_common,
model_lower=(agent.model or "").lower(),
is_openrouter=_is_or,
is_nous=_is_nous,
@@ -2321,18 +2297,12 @@ def build_api_kwargs(agent, api_messages: list, tools_for_api: list | None = Non
is_tokenhub=_is_tokenhub,
is_lmstudio=_is_lmstudio,
is_custom_provider=agent.provider == "custom",
ollama_num_ctx=agent._ollama_num_ctx,
provider_preferences=_prefs or None,
openrouter_min_coding_score=agent.openrouter_min_coding_score,
qwen_prepare_fn=agent._qwen_prepare_chat_messages if _is_qwen else None,
qwen_prepare_inplace_fn=agent._qwen_prepare_chat_messages_inplace if _is_qwen else None,
qwen_session_metadata=_qwen_meta,
fixed_temperature=_fixed_temp,
omit_temperature=_omit_temp,
supports_reasoning=agent._supports_reasoning_extra_body(),
github_reasoning_extra=agent._github_models_reasoning_extra_body() if _is_gh else None,
lmstudio_reasoning_options=agent._lmstudio_reasoning_options_cached() if _is_lmstudio else None,
anthropic_max_output=_ant_max,
provider_name=agent.provider,
)