Files

4646 lines
195 KiB
Python

"""Patched ChatOpenAI that preserves thought_signature for Gemini thinking models.
When using Gemini with thinking enabled via an OpenAI-compatible gateway (e.g.
Vertex AI, Google AI Studio, or any proxy), the API requires that the
``thought_signature`` field on tool-call objects is echoed back verbatim in
every subsequent request.
The OpenAI-compatible gateway stores the raw tool-call dicts (including
``thought_signature``) in ``additional_kwargs["tool_calls"]``, but standard
``langchain_openai.ChatOpenAI`` only serialises the standard fields (``id``,
``type``, ``function``) into the outgoing payload, silently dropping the
signature. That causes an HTTP 400 ``INVALID_ARGUMENT`` error:
Unable to submit request because function call `<tool>` in the N. content
block is missing a `thought_signature`.
This module fixes the problem by overriding ``_get_request_payload`` to
re-inject thinking metadata back into the outgoing payload for any assistant
message that originally carried it:
- ``thought_signature`` for Gemini-compatible tool calls.
- ``reasoning_content`` and the newer ``reasoning`` spelling used by vLLM and
other OpenAI-compatible providers. Both are normalized to
``additional_kwargs.reasoning_content`` for DeerFlow's existing clients.
Some OpenAI-compatible gateways (notably intranet DeepSeek) re-send the entire
``function.arguments`` JSON on every streamed chunk instead of a true delta.
LangChain concatenates those snapshots, so ``write_file`` JSON becomes invalid
until the final message and the sandbox can only refresh once. This adapter
rewrites growing snapshots into suffix deltas before LangChain merges them.
For gateways that buffer ``write_file.content`` completely, the opt-in
``write_file_transport=buffered_gateway`` adapter keeps the original
model-facing and graph-facing ``write_file`` contract. It first obtains only
the target metadata, then requests the body over an isolated ordinary-content
stream and exposes it downstream as standard incremental ``write_file``
argument chunks. Provider reasoning remains separate from file bytes; no extra
model-authored JSON or delimiter is required. The earlier
``artifact_renderer`` handshake and ``text_bridge`` mode remain available for
configuration compatibility. Unconfigured models stay on the native path.
"""
from __future__ import annotations
import asyncio
import contextvars
import json
import logging
import re
import time
import uuid
import warnings
from collections.abc import AsyncIterator, Iterator, Mapping
from dataclasses import dataclass, field
from typing import Any, Literal
from langchain_core.language_models import LanguageModelInput
from langchain_core.messages import AIMessage, AIMessageChunk, ToolMessage
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
from langchain_openai import ChatOpenAI
from deerflow.models.chat_stream_state import ChatStreamState, needs_qwen_flash_next_integrity
from deerflow.models.dsml_tool_calls import DSMLToolCallRecovery
from deerflow.models.message_ordering import normalize_chat_payload_messages
from deerflow.models.model_exchange_log import (
bind_exchange,
finish_exchange,
finish_owned_exchange,
open_exchange,
remember_final_message,
remember_provider_chunk,
remember_raw_response,
remember_request,
unbind_exchange,
)
from deerflow.utils.stream_text import InlineThinkTagFilter
logger = logging.getLogger(__name__)
_SAFE_FINISH_REASONS = frozenset(
{
"stop",
"length",
"tool_calls",
"function_call",
"content_filter",
"end_turn",
}
)
_TOOL_SIGNATURES_KEY = "_deerflow_tool_call_signatures"
_PYDANTIC_SERIALIZER_WARNING = r"^Pydantic serializer warnings:"
_WRITE_FILE_TOOL_NAME = "write_file"
_BEGIN_WRITE_FILE_TOOL_NAME = "begin_write_file"
_WRITE_FILE_BRIDGE_CALL_PREFIX = "call_deerflow_wfbridge_"
_WRITE_FILE_BRIDGE_INCOMPLETE = "__deerflow_stream_not_committed__"
_ARTIFACT_RENDERER_PREREQUISITE_TOOLS = frozenset(
{
# These tools can gather source material without becoming an alternate
# file-writing path. If a renderer discovers that evidence is still
# missing, the current body transaction is deferred and LangGraph runs
# the ordinary tool turn before a fresh prepare/body transaction.
"glob",
"grep",
"ls",
"read_file",
}
)
_TEXT_BRIDGE_SELECTION_INSTRUCTION = """<write_file_text_bridge>
This model uses DeerFlow's compatibility transport for file creation.
The real `write_file` function is intentionally hidden from this request.
Whenever an instruction requires `write_file`, call `begin_write_file` with only
the final absolute path, a short description, and the same `append` intent you
would pass to `write_file`. Do not put file content in that tool call. Call it
only when a file really must be created, overwritten, or appended; ordinary
conversational answers must remain ordinary assistant text. Call
`begin_write_file` by itself, never in parallel with another tool. After it
succeeds, the next model invocation will request the exact file body.
</write_file_text_bridge>"""
_TEXT_BRIDGE_BODY_INSTRUCTION = """<write_file_text_bridge_body>
The previous `begin_write_file` call was accepted. Output the complete file body
for the prepared path now. Output the file bytes as text only: no preface, no
explanation, and no surrounding Markdown fence unless that fence is itself part
of the file. Do not call any tool. Preserve all intended leading/trailing
whitespace and finish the whole file in this response. Keep private reasoning in
the provider's reasoning channel, never in the file body. If this provider can
only emit inline reasoning, exactly one leading `<think>...</think>` envelope is
treated as reasoning before file bytes begin.
</write_file_text_bridge_body>"""
_ARTIFACT_RENDERER_SELECTION_INSTRUCTION = """<write_file_artifact_renderer>
This model uses DeerFlow's compatibility renderer for file creation.
The provider-facing `write_file` in this request is a metadata-only preparation
control: its schema intentionally has no `content` field. Whenever an instruction
requires a file, call it with only the final absolute path, a short description,
and the intended `append` value. DeerFlow translates that call into an internal
prepare operation and requests the body separately. Call it only when a file
really must be created, overwritten, or appended; ordinary conversational
answers must remain ordinary assistant text. Call it by itself, never in
parallel with another tool.
</write_file_artifact_renderer>"""
_BUFFERED_GATEWAY_SELECTION_INSTRUCTION = """<write_file_buffered_gateway>
This deployment keeps the standard DeerFlow `write_file` capability but uses a
gateway that buffers long tool arguments. In this selection request, the
advertised `write_file` is intentionally metadata-only. When a file is needed,
call it with the final absolute path, a short description, and the intended
append value. DeerFlow will obtain the body through an isolated ordinary-text
stream and submit one complete standard `write_file` call. Do not emit file
content in this request. Call `write_file` only when a file is genuinely needed
and never in parallel with another tool; ordinary answers remain chat text.
</write_file_buffered_gateway>"""
_ARTIFACT_RENDERER_SYSTEM_INSTRUCTION = """You are DeerFlow's internal artifact renderer.
This is an isolated file-body transaction, not a chat response. Use the preceding
conversation and tool evidence as source material for the prepared artifact. The
ordinary assistant content channel is the exact file body: emit the complete file
bytes directly, with no JSON envelope, preface, explanation, or completion
summary. Markdown and code fences that belong in the requested file must be
emitted normally as part of the body. Keep private reasoning in the provider
reasoning channel. Complete the entire file in this response. If essential source
material is still missing, call exactly one of the explicitly advertised
prerequisite tools instead and emit no file-body text; DeerFlow will resume the
normal agent loop and start a fresh prepared transaction afterward."""
WriteFileTransport = Literal[
"native",
"buffered_gateway",
"artifact_renderer",
"text_bridge",
]
_WRITE_FILE_BRIDGE_TRANSPORTS = frozenset(
{"buffered_gateway", "artifact_renderer", "text_bridge"}
)
_ARTIFACT_BODY_TRANSPORTS = frozenset(
{"buffered_gateway", "artifact_renderer"}
)
class ArtifactRendererTransientProtocolError(RuntimeError):
"""A configured renderer stream ended before a safe tool/file commit.
No sandbox mutation has happened when this is raised, so the LLM retry
middleware may safely re-run the provider call. Deterministic invariant
violations (different path, conflicting append intent, parallel side
effects) continue to use ordinary ``RuntimeError`` and are not retried.
"""
# OpenAI's generated Pydantic model may accept non-standard structured tool
# arguments through its no-validation response parser, then include the entire
# argument object in a serializer warning before our converter can normalize
# it. Install one process-wide, narrowly scoped rule rather than holding a
# ``warnings.catch_warnings`` context across network awaits (which is not
# concurrency-safe). ``structured_arg_chunks`` remains the safe diagnostic.
def _install_structured_arg_warning_filter() -> None:
warnings.filterwarnings(
"ignore",
message=_PYDANTIC_SERIALIZER_WARNING,
category=UserWarning,
module=r"pydantic\.main",
)
_install_structured_arg_warning_filter()
def _extract_tool_call_signature_records(raw_tool_calls: Any) -> list[dict[str, Any]]:
"""Extract only opaque tool signatures plus bounded association metadata.
LangChain drops provider extension fields while converting OpenAI response
objects into ``AIMessage`` instances. Keep the minimum metadata needed to
echo Gemini-compatible thought signatures on the next request. Tool names,
arguments, response content, and other raw provider fields are intentionally
not copied into this private checkpoint field.
"""
if not isinstance(raw_tool_calls, list):
return []
records: list[dict[str, Any]] = []
for position, raw_tool_call in enumerate(raw_tool_calls):
if not isinstance(raw_tool_call, Mapping):
continue
candidates: list[tuple[str, Any]] = [
("top_snake", raw_tool_call.get("thought_signature")),
("top_camel", raw_tool_call.get("thoughtSignature")),
]
for extra_key, extra_label in (
("extra_content", "extra_snake"),
("extraContent", "extra_camel"),
):
extra_content = raw_tool_call.get(extra_key)
google = extra_content.get("google") if isinstance(extra_content, Mapping) else None
if isinstance(google, Mapping):
candidates.extend(
[
(f"{extra_label}_google_snake", google.get("thought_signature")),
(f"{extra_label}_google_camel", google.get("thoughtSignature")),
]
)
location = ""
signature = None
for candidate_location, candidate_signature in candidates:
if isinstance(candidate_signature, str) and candidate_signature:
location = candidate_location
signature = candidate_signature
break
if signature is None:
continue
raw_index = raw_tool_call.get("index")
try:
index = int(raw_index) if raw_index is not None else position
except (TypeError, ValueError):
index = position
record: dict[str, Any] = {
# Do not name this field ``index``: LangChain's chunk merger treats
# list items with that key as stream fragments and concatenates
# their string values, corrupting repeated opaque signatures.
"tool_index": index,
"location": location,
"signature": signature,
}
call_id = raw_tool_call.get("id")
if isinstance(call_id, str) and call_id:
record["id"] = call_id
records.append(record)
return records
def _append_tool_call_signature_metadata(
message: AIMessage | AIMessageChunk,
records: list[dict[str, Any]],
) -> AIMessage | AIMessageChunk:
if not records:
return message
additional_kwargs = dict(message.additional_kwargs)
existing = additional_kwargs.get(_TOOL_SIGNATURES_KEY)
combined = [dict(record) for record in existing if isinstance(record, Mapping)] if isinstance(existing, list) else []
combined.extend(records)
additional_kwargs[_TOOL_SIGNATURES_KEY] = combined
return message.model_copy(update={"additional_kwargs": additional_kwargs})
def _restore_signature_at_location(payload_tool_call: dict[str, Any], record: Mapping[str, Any]) -> None:
signature = record.get("signature")
location = record.get("location")
if not isinstance(signature, str) or not signature:
return
if location == "top_snake":
payload_tool_call["thought_signature"] = signature
return
if location == "top_camel":
payload_tool_call["thoughtSignature"] = signature
return
nested_locations = {
"extra_snake_google_snake": ("extra_content", "thought_signature"),
"extra_snake_google_camel": ("extra_content", "thoughtSignature"),
"extra_camel_google_snake": ("extraContent", "thought_signature"),
"extra_camel_google_camel": ("extraContent", "thoughtSignature"),
}
nested_path = nested_locations.get(location)
if nested_path is None:
return
extra_key, signature_key = nested_path
extra_content = payload_tool_call.get(extra_key)
if not isinstance(extra_content, dict):
extra_content = {}
payload_tool_call[extra_key] = extra_content
google = extra_content.get("google")
if not isinstance(google, dict):
google = {}
extra_content["google"] = google
google[signature_key] = signature
def _safe_metric_name(value: Any, *, fallback: str, limit: int = 80) -> str:
"""Return a bounded, low-cardinality value suitable for structured logs."""
text = str(value or "").strip()
if not text:
return fallback
if not re.fullmatch(r"[A-Za-z0-9_.:/-]+", text):
return fallback
return text[:limit]
def _safe_completion_id(value: Any) -> str:
"""Return a provider completion id only when it is an opaque ASCII token.
Completion ids are useful for joining our compatibility summary to an
upstream gateway log. Unlike the more general metric sanitizer, this
deliberately rejects URL separators and overlong values instead of
truncating them, so a malformed provider cannot turn the field into a
partial URL, response body, or log payload.
"""
if not isinstance(value, str):
return "none"
if value != value.strip() or not re.fullmatch(
r"[A-Za-z0-9][A-Za-z0-9_.-]{0,79}",
value,
):
return "none"
return value
def _safe_finish_reason(value: Any) -> str:
reason = str(value or "").strip().lower()
return reason if reason in _SAFE_FINISH_REASONS else ("none" if not reason else "other")
def _safe_upstream_code(value: Any) -> str:
"""Expose only conventional numeric provider codes in exception strings."""
text = str(value or "").strip()
return text if text.isascii() and text.isdigit() and len(text) <= 10 else "other"
@dataclass(frozen=True, slots=True)
class _PendingWriteFileBridge:
"""Validated, one-shot hand-off from ``begin_write_file`` to body streaming."""
begin_tool_call_id: str
write_tool_call_id: str
path: str
description: str
append: bool
allow_implicit_think_close: bool
@dataclass(slots=True)
class _SelectionAliasCallState:
"""Buffered provider alias until metadata-only vs real writer is known."""
index: int
call_id: str = ""
call_type: str = "function"
name: str = ""
arguments: str = ""
decision: Literal["pending", "begin", "write"] = "pending"
emitted: bool = False
class _IncrementalArgumentAccumulator:
"""Accumulate delta or growing-snapshot arguments without quadratic copies."""
__slots__ = ("_length", "_parts")
def __init__(self) -> None:
self._parts: list[str] = []
self._length = 0
def _is_prefix_of(self, candidate: str) -> bool:
if len(candidate) < self._length:
return False
offset = 0
for part in self._parts:
end = offset + len(part)
if candidate[offset:end] != part:
return False
offset = end
return True
def merge(self, fragment: str) -> str:
"""Return only the newly added logical suffix."""
if not fragment:
return ""
if not self._parts:
self._parts.append(fragment)
self._length = len(fragment)
return fragment
if self._is_prefix_of(fragment):
suffix = fragment[self._length :]
# A growing snapshot already contains every prior part. Collapse
# the rope so materialization remains one final join.
self._parts = [fragment]
self._length = len(fragment)
return suffix
self._parts.append(fragment)
self._length += len(fragment)
return fragment
def materialize(self) -> str:
return "".join(self._parts)
class _IncrementalJsonStringDecoder:
"""Decode one JSON string across arbitrary fragment boundaries."""
__slots__ = ("_capture", "_digits", "_mode", "_parts", "complete")
_ESCAPES = {
'"': '"',
"\\": "\\",
"/": "/",
"b": "\b",
"f": "\f",
"n": "\n",
"r": "\r",
"t": "\t",
}
def __init__(self, *, capture: bool) -> None:
self._capture = capture
self._digits = ""
self._mode: Literal[
"normal",
"escape",
"unicode",
"low_backslash",
"low_u",
"low_unicode",
] = "normal"
self._parts: list[str] = []
self.complete = False
def _emit(self, value: str, emitted: list[str]) -> None:
if self._capture:
self._parts.append(value)
emitted.append(value)
def feed_char(self, char: str) -> str:
if self.complete:
raise ValueError("characters after JSON string")
emitted: list[str] = []
if self._mode == "normal":
if char == '"':
self.complete = True
elif char == "\\":
self._mode = "escape"
elif ord(char) < 0x20 or 0xD800 <= ord(char) <= 0xDFFF:
raise ValueError("invalid JSON string character")
else:
self._emit(char, emitted)
elif self._mode == "escape":
if char in self._ESCAPES:
self._emit(self._ESCAPES[char], emitted)
self._mode = "normal"
elif char == "u":
self._digits = ""
self._mode = "unicode"
else:
raise ValueError("invalid JSON escape")
elif self._mode in {"unicode", "low_unicode"}:
if char not in "0123456789abcdefABCDEF":
raise ValueError("invalid JSON unicode escape")
self._digits += char
if len(self._digits) == 4:
codepoint = int(self._digits, 16)
if self._mode == "unicode" and 0xD800 <= codepoint <= 0xDBFF:
# Never expose half of a UTF-16 surrogate pair.
self._digits = f"{codepoint:04x}"
self._mode = "low_backslash"
elif self._mode == "low_unicode":
high = int(self._parts.pop(), 16)
if not 0xDC00 <= codepoint <= 0xDFFF:
raise ValueError("invalid JSON low surrogate")
combined = 0x10000 + ((high - 0xD800) << 10) + (
codepoint - 0xDC00
)
self._emit(chr(combined), emitted)
self._digits = ""
self._mode = "normal"
elif 0xDC00 <= codepoint <= 0xDFFF:
raise ValueError("unexpected JSON low surrogate")
else:
self._emit(chr(codepoint), emitted)
self._digits = ""
self._mode = "normal"
elif self._mode == "low_backslash":
if char != "\\":
raise ValueError("missing JSON low surrogate")
# Store the high surrogate as an internal marker. It is removed
# before the combined Unicode scalar is emitted.
self._parts.append(self._digits)
self._digits = ""
self._mode = "low_u"
elif self._mode == "low_u":
if char != "u":
raise ValueError("missing JSON low surrogate")
self._digits = ""
self._mode = "low_unicode"
return "".join(emitted)
def value(self) -> str:
if not self.complete:
raise ValueError("incomplete JSON string")
return "".join(self._parts)
class _IncrementalTopLevelStringExtractor:
"""Single-pass extractor for one root-object JSON string property."""
__slots__ = (
"_container_stack",
"_content_parts",
"_current_key",
"_decoder",
"_expected_key",
"_phase",
"_primitive_started",
"complete",
"consumed_characters",
"found",
)
def __init__(self, expected_key: str) -> None:
self._container_stack: list[str] = []
self._content_parts: list[str] = []
self._current_key = ""
self._decoder: _IncrementalJsonStringDecoder | None = None
self._expected_key = expected_key
self._phase: Literal[
"start",
"key_or_end",
"key",
"colon",
"value",
"content",
"skip_string",
"skip_container",
"skip_primitive",
"after_value",
"done",
] = "start"
self._primitive_started = False
self.complete = False
self.consumed_characters = 0
self.found = False
def _start_skipped_value(self, char: str) -> None:
if char == '"':
self._decoder = _IncrementalJsonStringDecoder(capture=False)
self._phase = "skip_string"
elif char in "{[":
self._container_stack = ["}" if char == "{" else "]"]
self._decoder = None
self._phase = "skip_container"
elif char in "-0123456789tfn":
self._primitive_started = True
self._phase = "skip_primitive"
else:
raise ValueError("invalid JSON value")
def feed(self, fragment: str) -> str:
emitted: list[str] = []
index = 0
while index < len(fragment):
char = fragment[index]
self.consumed_characters += 1
reprocess = False
if self._phase == "start":
if char.isspace():
pass
elif char == "{":
self._phase = "key_or_end"
else:
raise ValueError("tool arguments must be a JSON object")
elif self._phase == "key_or_end":
if char.isspace():
pass
elif char == "}":
self._phase = "done"
self.complete = True
elif char == '"':
self._decoder = _IncrementalJsonStringDecoder(capture=True)
self._phase = "key"
else:
raise ValueError("invalid JSON object key")
elif self._phase == "key":
assert self._decoder is not None
self._decoder.feed_char(char)
if self._decoder.complete:
self._current_key = self._decoder.value()
self._decoder = None
self._phase = "colon"
elif self._phase == "colon":
if char.isspace():
pass
elif char == ":":
self._phase = "value"
else:
raise ValueError("missing JSON object colon")
elif self._phase == "value":
if char.isspace():
pass
elif self._current_key == self._expected_key:
if self.found:
raise ValueError(f"duplicate {self._expected_key} property")
if char != '"':
raise ValueError(
f"{self._expected_key} must be a JSON string"
)
self.found = True
self._decoder = _IncrementalJsonStringDecoder(capture=True)
self._phase = "content"
else:
self._start_skipped_value(char)
elif self._phase == "content":
assert self._decoder is not None
decoded = self._decoder.feed_char(char)
if decoded:
self._content_parts.append(decoded)
emitted.append(decoded)
if self._decoder.complete:
self._decoder = None
self._phase = "after_value"
elif self._phase == "skip_string":
assert self._decoder is not None
self._decoder.feed_char(char)
if self._decoder.complete:
self._decoder = None
self._phase = "after_value"
elif self._phase == "skip_container":
if self._decoder is not None:
self._decoder.feed_char(char)
if self._decoder.complete:
self._decoder = None
elif char == '"':
self._decoder = _IncrementalJsonStringDecoder(capture=False)
elif char in "{[":
self._container_stack.append("}" if char == "{" else "]")
elif char in "}]":
if not self._container_stack or char != self._container_stack[-1]:
raise ValueError("mismatched JSON container delimiter")
self._container_stack.pop()
if not self._container_stack:
self._phase = "after_value"
elif self._phase == "skip_primitive":
if char in ",}" or char.isspace():
self._phase = "after_value"
reprocess = True
elif self._phase == "after_value":
if char.isspace():
pass
elif char == ",":
self._phase = "key_or_end"
elif char == "}":
self._phase = "done"
self.complete = True
else:
raise ValueError("invalid JSON object delimiter")
elif self._phase == "done":
if not char.isspace():
raise ValueError("characters after JSON object")
if reprocess:
self.consumed_characters -= 1
continue
index += 1
return "".join(emitted)
def content(self) -> str:
if not self.found:
raise ValueError(f"missing {self._expected_key} property")
return "".join(self._content_parts)
@dataclass(slots=True)
class _WriteFileBridgeStreamState:
"""Invocation-local state; never shared through the model instance."""
transport: WriteFileTransport
pending: _PendingWriteFileBridge | None = None
started: bool = False
closed: bool = False
aborted: bool = False
inline_think_filter: InlineThinkTagFilter = field(
default_factory=lambda: InlineThinkTagFilter(leading_only=True)
)
markdown_fence_projection: _MarkdownFenceProjection | None = None
selection_write_file_alias: bool = False
selection_alias_calls: dict[int, _SelectionAliasCallState] = field(
default_factory=dict
)
selection_alias_early_stop: bool = False
# ``buffered_gateway`` performs the metadata selection and ordinary-text
# body request inside one ChatModel stream. LangGraph therefore receives
# only the final canonical write_file call and never executes an internal
# preparation tool.
buffered_gateway_body_request: bool = False
selection_alias_has_native_reasoning: bool = False
selection_alias_saw_think_open: bool = False
selection_alias_implicit_think_close: bool = False
selection_alias_think_probe_suffix: str = ""
renderer_prerequisite_tools: frozenset[str] = field(default_factory=frozenset)
renderer_deferred_tool_names: dict[int, str] = field(default_factory=dict)
renderer_deferred: bool = False
renderer_leading_whitespace: str = ""
renderer_html_reference: str | None = None
renderer_html_probe: str = ""
renderer_html_probe_resolved: bool = False
renderer_html_recovered: bool = False
renderer_html_suspected_truncated: bool = False
renderer_html_content: list[str] = field(default_factory=list)
renderer_html_references: dict[str, str] = field(default_factory=dict)
renderer_fallback_active: bool = False
renderer_fallback_index: int | None = None
renderer_fallback_call_id: str = ""
renderer_fallback_name: str = ""
renderer_fallback_arguments: _IncrementalArgumentAccumulator = field(
default_factory=_IncrementalArgumentAccumulator
)
renderer_fallback_content: _IncrementalTopLevelStringExtractor = field(
default_factory=lambda: _IncrementalTopLevelStringExtractor("content")
)
renderer_fallback_control_filter: InlineThinkTagFilter = field(
default_factory=lambda: InlineThinkTagFilter(leading_only=True)
)
class _MarkdownFenceProjection:
"""Project an optional response-level Markdown fence out of a body stream.
Chat-oriented models occasionally wrap an entire ``.md`` artifact in a
`````markdown`` response fence even though the renderer requested raw file
bytes. A leading fence is ambiguous while the response is still running:
it may instead be an intentional first code block followed by more file
content. This projection therefore streams the likely body immediately,
retains a small terminal guard, and validates the *whole* response at the
safe finish frame.
If the response is a complete outer wrapper, only that first and last line
are removed. If it is not, ``finish`` returns an exact replacement so the
bridge can append a duplicate JSON ``content`` key (last-key-wins), restoring
every original byte before ToolNode sees the call. Native models, chat
messages, non-Markdown artifacts, and inner fences never enter this class.
"""
_OPEN_RE = re.compile(
r"\A[\ufeff \t\r\n]*```(?:markdown|md)[ \t]*\r?\n",
re.IGNORECASE,
)
_CLOSE_RE = re.compile(
r"(?P<linebreak>\r?\n)[ \t]*```[ \t]*(?:\r?\n)?[ \t\r\n]*\Z"
)
_OPEN_MARKERS = ("```markdown", "```md")
_PREFIX_LIMIT = 512
_TERMINAL_GUARD = 64
def __init__(self) -> None:
self._mode: Literal["probe", "plain", "candidate"] = "probe"
self._prefix = ""
self._raw_candidate = ""
self._opening_end = 0
self._tail = ""
self._emitted_length = 0
@classmethod
def _could_still_be_opening(cls, value: str) -> bool:
candidate = value.lstrip("\ufeff \t\r\n").lower()
if not candidate:
return True
for marker in cls._OPEN_MARKERS:
if marker.startswith(candidate):
return True
if candidate.startswith(marker) and not candidate[len(marker) :].strip(
" \t\r"
):
return True
return False
def push(self, text: str) -> str:
"""Return bytes safe to expose as the current streamed file prefix."""
if not text:
return ""
if self._mode == "plain":
return text
if self._mode == "candidate":
self._raw_candidate += text
return self._push_candidate_body(text)
self._prefix += text
opening = self._OPEN_RE.match(self._prefix)
if opening is not None:
self._mode = "candidate"
self._raw_candidate = self._prefix
self._opening_end = opening.end()
body = self._prefix[self._opening_end :]
self._prefix = ""
return self._push_candidate_body(body)
if len(self._prefix) <= self._PREFIX_LIMIT and self._could_still_be_opening(
self._prefix
):
return ""
self._mode = "plain"
confirmed = self._prefix
self._prefix = ""
return confirmed
def _push_candidate_body(self, text: str) -> str:
self._tail += text
if len(self._tail) <= self._TERMINAL_GUARD:
return ""
confirmed = self._tail[: -self._TERMINAL_GUARD]
self._tail = self._tail[-self._TERMINAL_GUARD :]
self._emitted_length += len(confirmed)
return confirmed
def finish(self) -> tuple[str, str | None]:
"""Return ``(tail, replacement)`` after whole-response validation.
``replacement`` is populated only when speculative bytes have already
streamed but the final whole-file interpretation differs. The caller
then repairs the canonical tool arguments with a duplicate JSON key;
no sandbox write occurs until that corrected object is complete.
"""
if self._mode == "probe":
tail = self._prefix
self._prefix = ""
self._mode = "plain"
return tail, None
if self._mode == "plain":
return "", None
raw = self._raw_candidate
closing = self._CLOSE_RE.search(raw, self._opening_end)
if closing is None or not self._has_balanced_inner_fences(
raw[self._opening_end : closing.start()]
):
final_content = raw
else:
final_content = (
raw[self._opening_end : closing.start()]
+ closing.group("linebreak")
)
speculative_prefix = raw[
self._opening_end : self._opening_end + self._emitted_length
]
self._tail = ""
if final_content.startswith(speculative_prefix):
return final_content[self._emitted_length :], None
return "", final_content
@staticmethod
def _has_balanced_inner_fences(body: str) -> bool:
"""Do not mistake separate Markdown code blocks for one outer wrapper.
A bare closer outside an inner block closes the proposed outer fence
early. An unclosed inner block would consume the proposed final fence.
In either case retain the original response instead of deleting bytes.
"""
active: tuple[str, int] | None = None
for line in body.splitlines():
match = re.match(r"^ {0,3}(`{3,}|~{3,})(.*)$", line)
if match is None:
continue
marker, suffix = match.groups()
if active is not None:
if marker[0] == active[0] and len(marker) >= active[1] and not suffix.strip():
active = None
elif marker[0] == "`" and not suffix.strip():
return False
else:
active = (marker[0], len(marker))
return active is None
@dataclass(slots=True)
class _ToolCallStreamDiagnostics:
"""Per-invocation, content-free diagnostics for OpenAI-compatible streams.
Logs contain only counters, timings, and lengths. Transient parser state is
scoped to this invocation; prompt text, response content, request URLs, and
exception messages are never included in diagnostic logs.
"""
model_name: str
run_id: str = "none"
provider_completion_id: str = "none"
started_at: float = field(default_factory=time.monotonic)
first_chunk_at: float | None = None
first_tool_args_at: float | None = None
first_reasoning_at: float | None = None
chunk_count: int = 0
incoming_reasoning_chunks: int = 0
emitted_reasoning_chunks: int = 0
request_enable_thinking: str = "unset"
intentional_early_finish: bool = False
tool_arg_chunks: int = 0
initial_chunks: int = 0
delta_chunks: int = 0
cumulative_chunks: int = 0
repeated_chunks: int = 0
rewind_chunks: int = 0
empty_arg_chunks: int = 0
invalid_shapes: int = 0
message_as_delta_chunks: int = 0
root_tool_call_chunks: int = 0
singleton_tool_call_chunks: int = 0
structured_arg_chunks: int = 0
tool_id_resets: int = 0
repeated_tool_names: int = 0
raw_arg_first_chars: int | None = None
raw_arg_last_chars: int = 0
raw_arg_max_chars: int = 0
raw_arg_growths: int = 0
raw_arg_flats: int = 0
raw_arg_shrinks: int = 0
finish_reason: str = "none"
strict_stream_integrity: bool = False
text_state: ChatStreamState = field(default_factory=ChatStreamState)
snapshots: dict[int, str] = field(default_factory=dict)
call_ids: dict[int, str] = field(default_factory=dict)
argument_modes: dict[int, str] = field(default_factory=dict)
tool_name_snapshots: dict[int, str] = field(default_factory=dict)
complete_tool_names: set[str] = field(default_factory=set)
last_raw_lengths: dict[int, int] = field(default_factory=dict)
seen_indices: set[int] = field(default_factory=set)
_logged: bool = False
def observe_chunk(self, chunk: Mapping[str, Any]) -> None:
self.chunk_count += 1
if self.first_chunk_at is None:
self.first_chunk_at = time.monotonic()
self.provider_completion_id = _safe_completion_id(chunk.get("id"))
choices = _stream_choices(chunk)
# The converter consumes the first choice only. Count a frame once,
# even if the gateway duplicates reasoning across aliases/containers.
if choices and any(_extract_reasoning_content(choices[0].get(key)) for key in ("delta", "message")):
self.incoming_reasoning_chunks += 1
if self.first_reasoning_at is None:
self.first_reasoning_at = time.monotonic()
for choice in choices:
reason = choice.get("finish_reason")
if reason:
self.finish_reason = _safe_finish_reason(reason)
def mark_invalid(self) -> None:
self.invalid_shapes += 1
def observe_argument(self, *, index: int, raw_length: int, classification: str) -> None:
self.tool_arg_chunks += 1
if self.first_tool_args_at is None:
self.first_tool_args_at = time.monotonic()
if self.raw_arg_first_chars is None:
self.raw_arg_first_chars = raw_length
self.raw_arg_last_chars = raw_length
self.raw_arg_max_chars = max(self.raw_arg_max_chars, raw_length)
previous_length = self.last_raw_lengths.get(index)
if previous_length is not None:
if raw_length > previous_length:
self.raw_arg_growths += 1
elif raw_length == previous_length:
self.raw_arg_flats += 1
else:
self.raw_arg_shrinks += 1
self.last_raw_lengths[index] = raw_length
if classification == "initial":
self.initial_chunks += 1
elif classification == "delta":
self.delta_chunks += 1
elif classification == "cumulative":
self.cumulative_chunks += 1
elif classification == "repeated":
self.repeated_chunks += 1
elif classification == "rewind":
self.rewind_chunks += 1
def log_summary(self, error: BaseException | None = None) -> None:
# This runs in the stream's finally block. Malformed diagnostic input
# or a failing deployment log handler must not replace the original
# provider exception/cancellation, or turn a successful answer into a
# failed call that the retry middleware sends to the model again.
try:
self._log_summary(error)
except Exception:
pass
def _log_summary(self, error: BaseException | None = None) -> None:
if self._logged:
return
self._logged = True
# One bounded summary per stream (never per token). Zero reasoning
# counts are useful too: they distinguish no upstream reasoning from
# reasoning that was lost during normalization.
now = time.monotonic()
total_ms = max(0, int((now - self.started_at) * 1000))
first_chunk_ms = max(0, int((self.first_chunk_at - self.started_at) * 1000)) if self.first_chunk_at is not None else -1
first_tool_args_ms = max(0, int((self.first_tool_args_at - self.started_at) * 1000)) if self.first_tool_args_at is not None else -1
first_reasoning_ms = max(0, int((self.first_reasoning_at - self.started_at) * 1000)) if self.first_reasoning_at is not None else -1
if error is None:
status = "success"
error_type = "none"
elif isinstance(error, (asyncio.CancelledError, GeneratorExit)):
status = "cancelled"
error_type = _safe_metric_name(type(error).__name__, fallback="other")
else:
status = "error"
error_type = _safe_metric_name(type(error).__name__, fallback="other")
assembled_final_chars = sum(len(value) for value in self.snapshots.values())
assembled_max_chars = max((len(value) for value in self.snapshots.values()), default=0)
cancelled = isinstance(error, (asyncio.CancelledError, GeneratorExit))
valid_final_json = 0
invalid_final_json = 0
if not self.intentional_early_finish:
for assembled_arguments in self.snapshots.values():
if not assembled_arguments:
continue
try:
json.loads(assembled_arguments)
except (TypeError, ValueError):
invalid_final_json += 1
else:
valid_final_json += 1
self._record_truncation_event(
status=status,
error=error,
error_type=error_type,
total_ms=total_ms,
first_chunk_ms=first_chunk_ms,
)
log = (
logger.info
if cancelled
else (
logger.warning
if error is not None or self.invalid_shapes or invalid_final_json
else logger.info
)
)
log(
"OPENAI_TOOL_STREAM_SUMMARY model=%s run_id=%s provider_completion_id=%s "
"status=%s total_ms=%d "
"first_chunk_ms=%d first_tool_args_ms=%d chunks=%d tool_arg_chunks=%d "
"tool_calls=%d initial=%d delta=%d cumulative=%d repeated=%d rewind=%d "
"empty_args=%d invalid_shapes=%d message_as_delta=%d root_tool_calls=%d "
"singleton_tool_calls=%d structured_args=%d tool_id_resets=%d raw_arg_first_chars=%d "
"raw_arg_last_chars=%d raw_arg_max_chars=%d raw_arg_growths=%d "
"raw_arg_flats=%d raw_arg_shrinks=%d assembled_final_chars=%d "
"assembled_max_chars=%d valid_final_json=%d invalid_final_json=%d "
"finish_reason=%s error_type=%s incoming_reasoning_chunks=%d "
"emitted_reasoning_chunks=%d first_reasoning_ms=%d request_enable_thinking=%s "
"repeated_tool_names=%d",
self.model_name,
self.run_id,
self.provider_completion_id,
status,
total_ms,
first_chunk_ms,
first_tool_args_ms,
self.chunk_count,
self.tool_arg_chunks,
len(self.seen_indices) + self.tool_id_resets,
self.initial_chunks,
self.delta_chunks,
self.cumulative_chunks,
self.repeated_chunks,
self.rewind_chunks,
self.empty_arg_chunks,
self.invalid_shapes,
self.message_as_delta_chunks,
self.root_tool_call_chunks,
self.singleton_tool_call_chunks,
self.structured_arg_chunks,
self.tool_id_resets,
self.raw_arg_first_chars or 0,
self.raw_arg_last_chars,
self.raw_arg_max_chars,
self.raw_arg_growths,
self.raw_arg_flats,
self.raw_arg_shrinks,
assembled_final_chars,
assembled_max_chars,
valid_final_json,
invalid_final_json,
self.finish_reason,
error_type,
self.incoming_reasoning_chunks,
self.emitted_reasoning_chunks,
first_reasoning_ms,
self.request_enable_thinking,
self.repeated_tool_names,
)
def _record_truncation_event(
self,
*,
status: str,
error: BaseException | None,
error_type: str,
total_ms: int,
first_chunk_ms: int,
) -> None:
"""Persist an admin-visible event when a provider stream ended truncated.
Classification (only genuine truncations; the write_file bridge's
intentional early finishes and user cancellations are excluded):
- ``silent_no_terminal``: the stream ended "successfully" without a
terminal frame after streaming content — the silently committed half
answer this log exists to surface (non-strict models).
- ``incomplete_stream_error``: strict integrity mode raised
``IncompleteChatStreamError`` — a detected truncation being retried.
- ``length_truncated`` / ``content_filter``: the provider closed the
stream with a truncating finish reason.
- ``midstream_error``: a transport/provider error cut the stream after
at least one chunk (the error middleware retries afterwards).
"""
if self.intentional_early_finish or status == "cancelled":
return
event_type: str | None = None
if error is not None:
if error_type == "IncompleteChatStreamError":
event_type = "incomplete_stream_error"
elif self.chunk_count > 0:
event_type = "midstream_error"
elif self.finish_reason == "none":
if self.chunk_count > 0:
event_type = "silent_no_terminal"
elif self.finish_reason == "length":
event_type = "length_truncated"
elif self.finish_reason == "content_filter":
event_type = "content_filter"
if event_type is None:
return
try:
from deerflow.persistence.stream_truncation import record_stream_truncation_event
record_stream_truncation_event(
model_name=self.model_name,
event_type=event_type,
strict_mode=self.strict_stream_integrity,
finish_reason=self.finish_reason,
status=status,
error_type=error_type if error is not None else None,
chunk_count=self.chunk_count,
duration_ms=total_ms,
first_chunk_ms=first_chunk_ms,
provider_completion_id=self.provider_completion_id,
run_id=self.run_id,
)
except Exception:
pass
_ACTIVE_TOOL_STREAM: contextvars.ContextVar[_ToolCallStreamDiagnostics | None] = contextvars.ContextVar(
"deerflow_openai_tool_stream",
default=None,
)
_ACTIVE_WRITE_FILE_BRIDGE: contextvars.ContextVar[_WriteFileBridgeStreamState | None] = contextvars.ContextVar(
"deerflow_openai_write_file_bridge",
default=None,
)
_ACTIVE_DSML_RECOVERY: contextvars.ContextVar[DSMLToolCallRecovery | None] = contextvars.ContextVar(
"deerflow_openai_dsml_recovery",
default=None,
)
def _message_content_text(content: Any) -> str:
if isinstance(content, str):
return content
if not isinstance(content, list):
return ""
parts: list[str] = []
for item in content:
if isinstance(item, str):
parts.append(item)
elif isinstance(item, Mapping):
text = item.get("text")
if isinstance(text, str):
parts.append(text)
return "".join(parts)
def _tool_call_value(tool_call: Any, key: str, default: Any = None) -> Any:
if isinstance(tool_call, Mapping):
return tool_call.get(key, default)
return getattr(tool_call, key, default)
def _tool_call_args(tool_call: Any) -> dict[str, Any]:
raw = _tool_call_value(tool_call, "args", {})
return dict(raw) if isinstance(raw, Mapping) else {}
def _pending_write_file_bridge(messages: list[Any]) -> _PendingWriteFileBridge | None:
"""Return a pending bridge only for an immediately preceding successful handshake.
This deliberately performs no semantic inspection of ordinary assistant
text. A bridge can start only when the last checkpointed message is the
successful result of the explicit ``begin_write_file`` tool.
"""
if not messages or not isinstance(messages[-1], ToolMessage):
return None
result = messages[-1]
if str(getattr(result, "status", "") or "").lower() == "error":
return None
result_text = _message_content_text(getattr(result, "content", "")).strip()
if not result_text or result_text.lower().startswith("error"):
return None
try:
ready = json.loads(result_text)
except (TypeError, ValueError):
return None
if not isinstance(ready, Mapping) or ready.get("status") != "ready":
return None
begin_tool_call_id = str(getattr(result, "tool_call_id", "") or "").strip()
if not begin_tool_call_id:
return None
for message in reversed(messages[:-1]):
if not isinstance(message, AIMessage):
continue
tool_calls = message.tool_calls or []
# A body round is intentionally one-shot. Parallel calls would leave
# multiple ToolMessages around the control exchange and make it
# ambiguous which operation the generated body belongs to.
if len(tool_calls) != 1:
return None
for tool_call in reversed(tool_calls):
if str(_tool_call_value(tool_call, "id", "") or "") != begin_tool_call_id:
continue
if str(_tool_call_value(tool_call, "name", "") or "") != _BEGIN_WRITE_FILE_TOOL_NAME:
return None
args = _tool_call_args(tool_call)
path = str(args.get("path") or "").strip()
description = str(args.get("description") or "").strip()
append = args.get("append", False)
if not path or not path.startswith("/") or not isinstance(append, bool):
return None
ready_path = str(ready.get("path") or "").strip()
ready_append = ready.get("append", False)
if (
ready_path != path
or not isinstance(ready_append, bool)
or ready_append != append
):
return None
# Provider-generated tool-call ids may be reused on later turns.
# Include checkpoint message identities so each prepared write gets
# a stable *and transaction-unique* preview id.
begin_message_id = str(getattr(message, "id", "") or "")
result_message_id = str(getattr(result, "id", "") or "")
transaction_identity = "\x1f".join(
(begin_message_id, result_message_id)
)
# LangChain normally assigns message ids, but direct SDK callers
# and some restored legacy checkpoints may not have them. A fresh
# invocation nonce prevents a provider-reused begin id from
# colliding with an earlier virtual preview in that case.
transaction_token = (
uuid.uuid5(
uuid.NAMESPACE_OID,
f"{begin_tool_call_id}\x1f{transaction_identity}\x1f{path}",
).hex[:24]
if transaction_identity.strip("\x1f")
else uuid.uuid4().hex[:24]
)
return _PendingWriteFileBridge(
begin_tool_call_id=begin_tool_call_id,
write_tool_call_id=(
f"{_WRITE_FILE_BRIDGE_CALL_PREFIX}{transaction_token}"
),
path=path,
description=description or "Write the prepared file",
append=append,
allow_implicit_think_close=(
_message_uses_implicit_think_close(message)
),
)
# The matching tool call must be on the nearest preceding AI message.
return None
return None
def _payload_tool_name(tool: Any) -> str:
if not isinstance(tool, Mapping):
return ""
function = tool.get("function")
if isinstance(function, Mapping):
return str(function.get("name") or "")
return str(tool.get("name") or "")
def _selection_tool(tool: Any, *, provider_name: str | None = None) -> Any:
"""Return a strict provider schema for the internal prepare handshake.
``artifact_renderer`` deliberately exposes the metadata-only schema under
the familiar ``write_file`` name. Weak models often copy that name from the
lead-agent contract even when the real writer has been removed. The raw
response is translated back to ``begin_write_file`` before LangGraph sees
it, so ToolNode still executes only the server-owned preparation control.
"""
if not isinstance(tool, Mapping) or _payload_tool_name(tool) != _BEGIN_WRITE_FILE_TOOL_NAME:
return tool
function = tool.get("function")
if not isinstance(function, Mapping):
return tool
parameters = function.get("parameters")
if not isinstance(parameters, Mapping):
return tool
strict_parameters = dict(parameters)
# LangChain's injected-argument subset model does not retain the full
# Pydantic model's ``extra=forbid`` setting. Restore it at the actual
# provider boundary so a weak model is explicitly told that file bytes are
# not legal in this small control call.
strict_parameters["additionalProperties"] = False
if provider_name:
# The renderer can hand off as soon as these three small fields have
# arrived. Requiring all of them avoids guessing an append intent from
# an unfinished JSON object and lets us cancel a gateway that ignored
# the metadata-only schema before it spends the rest of the response
# generating a hidden ``content`` argument.
strict_parameters["required"] = ["description", "path", "append"]
strict_function = dict(function)
strict_function["parameters"] = strict_parameters
if provider_name:
strict_function["name"] = provider_name
strict_tool = dict(tool)
strict_tool["function"] = strict_function
return strict_tool
def _buffered_gateway_selection_tool(tool: Any) -> Any:
"""Expose the original writer as a strict metadata-only selector.
The provider continues to see the familiar ``write_file`` name. Only its
large ``content`` argument is removed for the selection request, avoiding
the gateway buffer that motivated this compatibility mode. The final
LangGraph/ToolNode call is still the untouched canonical writer schema.
"""
if not isinstance(tool, Mapping) or _payload_tool_name(tool) != _WRITE_FILE_TOOL_NAME:
return tool
function = tool.get("function")
if not isinstance(function, Mapping):
return tool
parameters = function.get("parameters")
if not isinstance(parameters, Mapping):
return tool
properties = parameters.get("properties")
if not isinstance(properties, Mapping):
return tool
metadata_properties = {
key: value
for key, value in properties.items()
if key in {"description", "path", "append"}
}
if not {"description", "path", "append"}.issubset(metadata_properties):
raise RuntimeError(
"buffered_gateway requires write_file description/path/append fields"
)
strict_parameters = dict(parameters)
strict_parameters["properties"] = metadata_properties
strict_parameters["required"] = ["description", "path", "append"]
strict_parameters["additionalProperties"] = False
strict_function = dict(function)
strict_function["parameters"] = strict_parameters
strict_tool = dict(tool)
strict_tool["function"] = strict_function
return strict_tool
def _message_uses_implicit_think_close(message: Any) -> bool:
"""Whether one model response demonstrated a template-injected open tag."""
content = _message_content_text(getattr(message, "content", ""))
lowered = content.lower()
open_at = lowered.find("<think>")
close_at = lowered.find("</think>")
return close_at >= 0 and (open_at < 0 or close_at < open_at)
def _strip_historical_begin_write_file_exchanges(
messages: list[Any],
) -> list[Any]:
"""Remove only reserved bridge calls/results from provider history.
A user can switch a thread from a bridge model to a native model, and
non-stream calls can reuse the same history. ``begin_write_file`` is an
internal transport control, so it must not be sent to a provider that
cannot see its schema. Exact tool names and call ids keep ordinary tool
history intact, including other calls made by the same assistant message.
"""
cleaned: list[Any] = []
changed = False
index = 0
while index < len(messages):
message = messages[index]
if not isinstance(message, Mapping) or message.get("role") != "assistant":
cleaned.append(message)
index += 1
continue
tool_calls = message.get("tool_calls")
if not isinstance(tool_calls, list):
cleaned.append(message)
index += 1
continue
begin_call_ids: set[str] = set()
for call in tool_calls:
function = call.get("function") if isinstance(call, Mapping) else None
call_id = str(call.get("id") or "") if isinstance(call, Mapping) else ""
if (
call_id
and isinstance(function, Mapping)
and function.get("name") == _BEGIN_WRITE_FILE_TOOL_NAME
):
begin_call_ids.add(call_id)
if not begin_call_ids:
cleaned.append(message)
index += 1
continue
changed = True
visible_calls = [
call
for call in tool_calls
if not (
isinstance(call, Mapping)
and str(call.get("id") or "") in begin_call_ids
and isinstance(call.get("function"), Mapping)
and call["function"].get("name") == _BEGIN_WRITE_FILE_TOOL_NAME
)
]
rewritten = dict(message)
if visible_calls:
rewritten["tool_calls"] = visible_calls
cleaned.append(rewritten)
else:
# A begin-only turn is transport control even when the provider
# attached a preface. Dropping it avoids an assistant→assistant
# sequence after its tool receipt is removed. Mixed real-tool
# turns retain their content and remaining calls above.
rewritten.pop("tool_calls", None)
# Tool call ids are scoped to this assistant turn. Remove matching
# begin receipts only from its immediately following result block;
# later turns are allowed to reuse the same provider-generated id.
index += 1
while index < len(messages):
result = messages[index]
if not isinstance(result, Mapping) or result.get("role") != "tool":
break
if str(result.get("tool_call_id") or "") not in begin_call_ids:
cleaned.append(result)
index += 1
return cleaned if changed else messages
def _hide_begin_write_file_for_native_request(payload: dict[str, Any]) -> None:
"""Keep the internal handshake out of native/non-stream provider calls."""
messages = payload.get("messages")
if isinstance(messages, list):
cleaned_messages = _strip_historical_begin_write_file_exchanges(messages)
if cleaned_messages is not messages:
payload["messages"] = cleaned_messages
tools = payload.get("tools")
has_write_file = False
if isinstance(tools, list):
visible_tools = [
tool for tool in tools if _payload_tool_name(tool) != _BEGIN_WRITE_FILE_TOOL_NAME
]
has_write_file = any(
_payload_tool_name(tool) == _WRITE_FILE_TOOL_NAME for tool in visible_tools
)
if len(visible_tools) != len(tools):
payload["tools"] = visible_tools
choice = payload.get("tool_choice")
if choice == _BEGIN_WRITE_FILE_TOOL_NAME:
if has_write_file:
payload["tool_choice"] = _WRITE_FILE_TOOL_NAME
else:
payload.pop("tool_choice", None)
return
if not isinstance(choice, dict):
return
function = choice.get("function")
if isinstance(function, dict) and function.get("name") == _BEGIN_WRITE_FILE_TOOL_NAME:
if has_write_file:
rewritten_function = dict(function)
rewritten_function["name"] = _WRITE_FILE_TOOL_NAME
rewritten_choice = dict(choice)
rewritten_choice["function"] = rewritten_function
payload["tool_choice"] = rewritten_choice
else:
payload.pop("tool_choice", None)
def _append_system_instruction(messages: list[Any], instruction: str) -> None:
"""Append transient bridge context for later normalization at the boundary."""
messages.append({"role": "system", "content": instruction})
def _payload_tool_arguments(call: Any) -> tuple[dict[str, Any], str]:
if not isinstance(call, Mapping):
return {}, ""
function = call.get("function")
if not isinstance(function, Mapping):
return {}, ""
raw = function.get("arguments")
if isinstance(raw, Mapping):
return dict(raw), json.dumps(raw, ensure_ascii=False, separators=(",", ":"))
if not isinstance(raw, str):
return {}, ""
try:
parsed = json.loads(raw)
except (TypeError, ValueError):
return {}, raw
return (dict(parsed) if isinstance(parsed, Mapping) else {}), raw
def _successful_read_file_contents(messages: list[Any]) -> dict[str, str]:
"""Map each path to its newest successful complete ``read_file`` result.
Calls are consumed in message order. Reused provider ids overwrite the
pending path and are popped with their result, so a later read cannot attach
its bytes to an older same-id request.
"""
pending_calls: dict[str, str] = {}
latest: dict[str, str] = {}
for message in messages:
if isinstance(message, AIMessage):
for tool_call in message.tool_calls or []:
if str(_tool_call_value(tool_call, "name", "") or "") != "read_file":
continue
args = _tool_call_args(tool_call)
path = str(args.get("path") or "")
call_id = str(_tool_call_value(tool_call, "id", "") or "")
if call_id and path:
pending_calls[call_id] = path
continue
if not isinstance(message, ToolMessage):
continue
call_id = str(getattr(message, "tool_call_id", "") or "")
path = pending_calls.pop(call_id, None)
if path is None:
continue
if str(getattr(message, "status", "") or "").lower() == "error":
continue
content = _message_content_text(getattr(message, "content", ""))
if content:
if re.search(r"\.(?:html?|xhtml)\Z", path, re.IGNORECASE) and not (
_is_complete_html_document(content)
):
# A later ranged/truncated read must not replace an earlier
# complete template that is still the only safe prefix source.
continue
latest[path] = content
return latest
def _is_overwrite_html_artifact(pending: _PendingWriteFileBridge) -> bool:
return not pending.append and bool(
re.search(r"\.(?:html?|xhtml)\Z", pending.path, re.IGNORECASE)
)
def _is_complete_html_document(content: str) -> bool:
"""Validate the recovered standalone envelope, not general HTML validity."""
stripped = content.lstrip("\ufeff \t\r\n")
if re.match(r"<!doctype\s+html(?:\s+[^>]*)?>", stripped, re.IGNORECASE) is None:
return False
patterns = (
r"<html(?:\s|>)",
r"<head(?:\s|>)",
r"</head\s*>",
r"<body(?:\s|>)",
r"</body\s*>",
r"</html\s*>\s*\Z",
)
matches = [re.search(pattern, stripped, re.IGNORECASE) for pattern in patterns]
if any(match is None for match in matches):
return False
positions = [match.start() for match in matches if match is not None]
return positions == sorted(positions)
def _probe_html_prefix_recovery(
content: str,
reference: str | None,
*,
final: bool,
) -> tuple[str, bool, bool, bool]:
"""Resolve a chunk-safe, evidence-backed HTML prefix projection.
Returns ``(projected, resolved, recovered, suspected_truncation)``. A prefix
is copied only when at least 48 exact leading bytes occur once in the latest
complete same-target read. Merely sharing a tag name is never sufficient.
"""
stripped = content.lstrip("\ufeff \t\r\n")
lowered = stripped.lower()
complete_starts = ("<!doctype", "<html")
internal_starts = ("<head", "<style", "<body", "<main", "<script")
complete_match = re.match(
r"(?:<!doctype\s+html(?:\s+[^>]*)?>|<html(?:\s|>))",
stripped,
re.IGNORECASE,
)
if complete_match is not None:
# Detection may use the stripped view, but complete documents retain
# their byte-exact BOM and leading whitespace.
return content, True, False, False
possible_complete = bool(lowered) and any(
start.startswith(lowered) for start in complete_starts
)
if possible_complete:
if not final:
return content, False, False, False
return content, True, False, True
possible_internal = bool(lowered) and any(
start.startswith(lowered) for start in internal_starts
)
internal_match = re.match(
r"<(head|style|body|main|script)(?:\s|>)",
stripped,
re.IGNORECASE,
)
if internal_match is None:
if possible_internal:
if not final:
return content, False, False, False
return content, True, False, True
return content, True, False, False
if not reference or not _is_complete_html_document(reference):
return content, True, False, True
if len(stripped) < 48:
return content, final, False, True
reference_stripped = reference.lstrip("\ufeff \t\r\n")
anchor = internal_match.group(1).lower()
head_open = re.search(r"<head(?:\s|>)", reference_stripped, re.IGNORECASE)
head_close = re.search(r"</head\s*>", reference_stripped, re.IGNORECASE)
body_open = re.search(r"<body(?:\s|>)", reference_stripped, re.IGNORECASE)
max_overlap = min(256, len(stripped))
min_overlap = 48
for overlap in range(max_overlap, min_overlap - 1, -1):
needle = stripped[:overlap]
first = reference_stripped.find(needle)
if first <= 0:
continue
if reference_stripped.find(needle, first + 1) >= 0:
continue
if anchor in {"style", "script"} and not (
head_open is not None
and head_close is not None
and head_open.start() < first < head_close.start()
):
continue
if anchor == "head" and (
head_open is None or first != head_open.start()
):
continue
if anchor == "body" and (
body_open is None or first != body_open.start()
):
continue
if anchor == "main" and (
body_open is None or first <= body_open.start()
):
continue
return (
f"{reference_stripped[:first]}{stripped}",
True,
True,
True,
)
if not final and len(stripped) < 256 and reference_stripped.count(
stripped[:min_overlap]
) > 1:
return content, False, False, True
return content, True, False, True
def _project_renderer_visible_text(
state: _WriteFileBridgeStreamState,
pending: _PendingWriteFileBridge,
text: str,
*,
final: bool = False,
) -> str:
"""Delay ambiguous prefaces and project the first HTML fragment safely."""
# An inline ``<think>...</think>`` chunk is consumed entirely by the
# reasoning filter before it reaches this projector. Treat that empty
# projection as "no file evidence yet". Resolving the HTML prefix probe on
# it would make the following harmless separator newlines look like file
# bytes; if the provider then falls back to a complete ``write_file`` call,
# the bridge would incorrectly reject the transition as mixed output.
if not text and not final:
return ""
if not state.started and not state.renderer_html_probe_resolved:
candidate = f"{state.renderer_leading_whitespace}{text}"
if candidate and not candidate.strip():
state.renderer_leading_whitespace = candidate
return ""
state.renderer_leading_whitespace = ""
if _is_overwrite_html_artifact(pending):
state.renderer_html_probe = f"{state.renderer_html_probe}{candidate}"
projected, resolved, recovered, suspected = _probe_html_prefix_recovery(
state.renderer_html_probe,
state.renderer_html_reference,
final=final,
)
state.renderer_html_recovered = recovered
state.renderer_html_suspected_truncated = suspected
if not resolved:
return ""
state.renderer_html_probe_resolved = True
state.renderer_html_probe = ""
text = projected
else:
text = candidate
if text and _is_overwrite_html_artifact(pending):
state.renderer_html_content.append(text)
return text
def _validate_renderer_artifact_complete(
state: _WriteFileBridgeStreamState,
pending: _PendingWriteFileBridge,
) -> None:
if not _is_overwrite_html_artifact(pending):
return
content = "".join(state.renderer_html_content)
stripped = content.lstrip("\ufeff \t\r\n").lower()
requires_complete_document = (
state.renderer_html_recovered
or state.renderer_html_suspected_truncated
or stripped.startswith(("<!doctype", "<html"))
)
if not requires_complete_document or _is_complete_html_document(content):
return
state.aborted = True
logger.warning(
"ARTIFACT_RENDERER_PROTOCOL_REJECTED "
"reason=incomplete_html_document finish_reason=stop"
)
raise ArtifactRendererTransientProtocolError(
"Artifact renderer returned an incomplete HTML document"
)
def _normalize_complete_renderer_html(
content: str,
reference: str | None,
) -> str:
projected, _resolved, recovered, suspected = _probe_html_prefix_recovery(
content,
reference,
final=True,
)
stripped = projected.lstrip("\ufeff \t\r\n").lower()
requires_complete_document = (
recovered
or suspected
or stripped.startswith(("<!doctype", "<html"))
)
if not requires_complete_document or _is_complete_html_document(projected):
return projected
raise ArtifactRendererTransientProtocolError(
"Artifact renderer returned an incomplete HTML document"
)
def _normalize_complete_renderer_content(
content: str,
*,
path: str,
append: bool,
html_reference: str | None,
preserve_leading_think_literal: bool,
) -> tuple[str, str]:
"""Apply the body renderer's reasoning and envelope rules atomically."""
think_filter = InlineThinkTagFilter(leading_only=True)
if preserve_leading_think_literal:
think_filter.confirm_visible_channel()
visible, reasoning = think_filter.push_parts(content)
unclosed_thinking = think_filter.has_unclosed_thinking
tail_visible, tail_reasoning = think_filter.finish_parts()
if unclosed_thinking:
raise ArtifactRendererTransientProtocolError(
"Artifact renderer ended inside a reasoning envelope"
)
visible = f"{visible}{tail_visible}"
reasoning = f"{reasoning}{tail_reasoning}"
if not append and re.search(r"\.(?:md|markdown)\Z", path, re.IGNORECASE):
projection = _MarkdownFenceProjection()
projected = projection.push(visible)
projected_tail, replacement = projection.finish()
visible = replacement if replacement is not None else f"{projected}{projected_tail}"
if not append and re.search(r"\.(?:html?|xhtml)\Z", path, re.IGNORECASE):
visible = _normalize_complete_renderer_html(visible, html_reference)
if not visible:
raise RuntimeError("Artifact renderer returned no file body")
return visible, reasoning
def _json_object_has_top_level_key(raw: str, expected_key: str) -> bool:
"""Recognize a top-level JSON object key in a possibly incomplete stream."""
stack: list[str] = []
index = 0
length = len(raw)
while index < length:
char = raw[index]
if char in " \t\r\n":
index += 1
continue
if char == '"':
index += 1
token: list[str] = []
escaped = False
while index < length:
char = raw[index]
if escaped:
token.append(char)
escaped = False
index += 1
continue
if char == "\\":
escaped = True
index += 1
continue
if char == '"':
break
token.append(char)
index += 1
if index >= length:
return False
index += 1
lookahead = index
while lookahead < length and raw[lookahead] in " \t\r\n":
lookahead += 1
if (
stack == ["{"]
and "".join(token) == expected_key
and lookahead < length
and raw[lookahead] == ":"
):
return True
continue
if char == "{":
stack.append("{")
elif char == "[":
stack.append("[")
elif char == "}" and stack and stack[-1] == "{":
stack.pop()
elif char == "]" and stack and stack[-1] == "[":
stack.pop()
index += 1
return False
def _decode_json_string_prefix(
raw: str,
start: int,
) -> tuple[str, int, bool]:
"""Decode one JSON string, including its currently complete prefix.
OpenAI-compatible gateways may split an escape sequence (including a
``\\uXXXX`` pair) across arbitrary SSE frames. The returned ``end`` is
the next input offset for a complete string and ``len(raw)`` otherwise.
Incomplete escapes are retained by the caller for the next cumulative
parse and are never exposed as file bytes.
"""
if start >= len(raw) or raw[start] != '"':
raise ValueError("expected a JSON string")
index = start + 1
safe_end = index
while index < len(raw):
char = raw[index]
if char == '"':
token = raw[start : index + 1]
try:
return json.loads(token), index + 1, True
except (TypeError, ValueError) as exc:
raise ValueError("invalid JSON string") from exc
if char == "\\":
if index + 1 >= len(raw):
break
escape = raw[index + 1]
if escape == "u":
if index + 6 > len(raw):
break
digits = raw[index + 2 : index + 6]
if not re.fullmatch(r"[0-9a-fA-F]{4}", digits):
raise ValueError("invalid JSON unicode escape")
index += 6
safe_end = index
continue
if escape not in {'"', "\\", "/", "b", "f", "n", "r", "t"}:
raise ValueError("invalid JSON escape")
index += 2
safe_end = index
continue
if ord(char) < 0x20:
raise ValueError("unescaped JSON control character")
index += 1
safe_end = index
inner = raw[start + 1 : safe_end]
# Do not expose half of an escaped UTF-16 surrogate pair. ``json.loads``
# accepts a lone high surrogate, but serializing that transient value into
# the canonical UTF-8 write_file stream is not safe. The next SSE frame
# will make the pair complete and the cumulative parse will include it.
if re.search(r"\\u[dD][89aAbB][0-9a-fA-F]{2}\Z", inner):
inner = inner[:-6]
try:
decoded = json.loads(f'"{inner}"')
except (TypeError, ValueError) as exc:
raise ValueError("invalid partial JSON string") from exc
return decoded, len(raw), False
def _partial_top_level_json_string(
raw: str,
expected_key: str,
) -> tuple[str, bool, bool]:
"""Return a top-level JSON string value before its object is complete.
The scanner is deliberately schema-small: it locates only a direct key of
the root object while still tracking nested objects/arrays and escaped
strings. This lets the artifact renderer project ``content`` incrementally
without trusting provider-authored ``path`` or ``append`` metadata.
"""
index = 0
while index < len(raw) and raw[index] in " \t\r\n":
index += 1
if index >= len(raw):
return "", False, False
if raw[index] != "{":
raise ValueError("tool arguments must be a JSON object")
stack = ["{"]
index += 1
while index < len(raw):
char = raw[index]
if char in " \t\r\n":
index += 1
continue
if char == '"':
token, end, complete = _decode_json_string_prefix(raw, index)
if not complete:
return "", False, False
lookahead = end
while lookahead < len(raw) and raw[lookahead] in " \t\r\n":
lookahead += 1
if (
stack == ["{"]
and lookahead < len(raw)
and raw[lookahead] == ":"
and token == expected_key
):
value_start = lookahead + 1
while value_start < len(raw) and raw[value_start] in " \t\r\n":
value_start += 1
if value_start >= len(raw):
return "", True, False
if raw[value_start] != '"':
raise ValueError(f"{expected_key} must be a JSON string")
value, _value_end, value_complete = _decode_json_string_prefix(
raw,
value_start,
)
return value, True, value_complete
index = end
continue
if char == "{":
stack.append("{")
elif char == "[":
stack.append("[")
elif char == "}":
if not stack or stack[-1] != "{":
raise ValueError("mismatched JSON object delimiter")
stack.pop()
elif char == "]":
if not stack or stack[-1] != "[":
raise ValueError("mismatched JSON array delimiter")
stack.pop()
index += 1
return "", False, False
def _partial_top_level_json_boolean(
raw: str,
expected_key: str,
) -> tuple[bool | None, bool, bool]:
"""Return one complete top-level boolean from an unfinished JSON object.
Compatible gateways sometimes serialize booleans as the canonical strings
``"true"``/``"false"``. Accept those just like the terminal validator,
but never infer a value from a prefix such as ``fal``.
"""
index = 0
while index < len(raw) and raw[index] in " \t\r\n":
index += 1
if index >= len(raw):
return None, False, False
if raw[index] != "{":
raise ValueError("tool arguments must be a JSON object")
stack = ["{"]
index += 1
while index < len(raw):
char = raw[index]
if char in " \t\r\n":
index += 1
continue
if char == '"':
token, end, complete = _decode_json_string_prefix(raw, index)
if not complete:
return None, False, False
lookahead = end
while lookahead < len(raw) and raw[lookahead] in " \t\r\n":
lookahead += 1
if (
stack == ["{"]
and lookahead < len(raw)
and raw[lookahead] == ":"
and token == expected_key
):
value_start = lookahead + 1
while value_start < len(raw) and raw[value_start] in " \t\r\n":
value_start += 1
if value_start >= len(raw):
return None, True, False
if raw[value_start] == '"':
value, value_end, value_complete = _decode_json_string_prefix(
raw,
value_start,
)
if not value_complete:
return None, True, False
normalized = _compatible_boolean(value)
if normalized is None:
raise ValueError(f"{expected_key} must be a JSON boolean")
while value_end < len(raw) and raw[value_end] in " \t\r\n":
value_end += 1
if value_end >= len(raw):
return None, True, False
if raw[value_end] not in ",}":
raise ValueError(f"{expected_key} must be a JSON boolean")
return normalized, True, True
remainder = raw[value_start:]
for literal, value in (("true", True), ("false", False)):
if remainder.startswith(literal):
value_end = value_start + len(literal)
while value_end < len(raw) and raw[value_end] in " \t\r\n":
value_end += 1
if value_end >= len(raw):
return None, True, False
if raw[value_end] not in ",}":
raise ValueError(
f"{expected_key} must be a JSON boolean"
)
return value, True, True
if len(remainder) < len(literal) and literal.startswith(remainder):
return None, True, False
raise ValueError(f"{expected_key} must be a JSON boolean")
index = end
continue
if char == "{":
stack.append("{")
elif char == "[":
stack.append("[")
elif char == "}":
if not stack or stack[-1] != "{":
raise ValueError("mismatched JSON object delimiter")
stack.pop()
elif char == "]":
if not stack or stack[-1] != "[":
raise ValueError("mismatched JSON array delimiter")
stack.pop()
index += 1
return None, False, False
def _top_level_json_string_is_delimited(raw: str, expected_key: str) -> bool:
"""Confirm a complete root string is followed by a JSON delimiter."""
index = 0
while index < len(raw) and raw[index] in " \t\r\n":
index += 1
if index >= len(raw) or raw[index] != "{":
return False
stack = ["{"]
index += 1
while index < len(raw):
char = raw[index]
if char in " \t\r\n":
index += 1
continue
if char == '"':
token, end, complete = _decode_json_string_prefix(raw, index)
if not complete:
return False
lookahead = end
while lookahead < len(raw) and raw[lookahead] in " \t\r\n":
lookahead += 1
if (
stack == ["{"]
and lookahead < len(raw)
and raw[lookahead] == ":"
and token == expected_key
):
value_start = lookahead + 1
while value_start < len(raw) and raw[value_start] in " \t\r\n":
value_start += 1
if value_start >= len(raw) or raw[value_start] != '"':
return False
_value, value_end, value_complete = _decode_json_string_prefix(
raw,
value_start,
)
if not value_complete:
return False
while value_end < len(raw) and raw[value_end] in " \t\r\n":
value_end += 1
return value_end < len(raw) and raw[value_end] in ",}"
index = end
continue
if char == "{":
stack.append("{")
elif char == "[":
stack.append("[")
elif char == "}":
if not stack or stack[-1] != "{":
return False
stack.pop()
elif char == "]":
if not stack or stack[-1] != "[":
return False
stack.pop()
index += 1
return False
def _complete_selection_alias_metadata(raw: str) -> dict[str, Any] | None:
"""Extract a safe prepare hand-off before a provider starts file bytes."""
try:
description, description_found, description_complete = (
_partial_top_level_json_string(raw, "description")
)
path, path_found, path_complete = _partial_top_level_json_string(
raw,
"path",
)
append, append_found, append_complete = _partial_top_level_json_boolean(
raw,
"append",
)
except ValueError:
# The terminal path reports a precise protocol failure. While the JSON
# is still arriving, an invalid-looking suffix may merely be split at a
# token boundary, so do not abort or guess here.
return None
if not all(
(
description_found,
description_complete,
path_found,
path_complete,
append_found,
append_complete,
)
):
return None
if not _top_level_json_string_is_delimited(
raw,
"description",
) or not _top_level_json_string_is_delimited(raw, "path"):
return None
description = description.strip() or "Write the prepared file"
path = path.strip()
if not path.startswith("/") or append is None:
return None
return {
"description": description,
"path": path,
"append": append,
}
def _pending_from_buffered_gateway_selection(
buffered: _SelectionAliasCallState,
metadata: Mapping[str, Any],
*,
allow_implicit_think_close: bool,
) -> _PendingWriteFileBridge:
"""Create a server-owned body transaction without a graph tool round."""
transaction_token = uuid.uuid5(
uuid.NAMESPACE_OID,
"\x1f".join(
(
buffered.call_id,
str(metadata["path"]),
uuid.uuid4().hex,
)
),
).hex[:24]
return _PendingWriteFileBridge(
# Retained for the legacy handshake data shape only. This id is never
# emitted to LangGraph or executed by ToolNode in buffered_gateway mode.
begin_tool_call_id=buffered.call_id,
write_tool_call_id=f"{_WRITE_FILE_BRIDGE_CALL_PREFIX}{transaction_token}",
path=str(metadata["path"]),
description=str(metadata["description"]),
append=bool(metadata["append"]),
# Enable close-only probing only when the selection response actually
# demonstrated that provider convention. Enabling it unconditionally
# would buffer an ordinary file body until the terminal frame and
# defeat the live preview this transport exists to restore.
allow_implicit_think_close=allow_implicit_think_close,
)
def _failed_tool_result(message: Any) -> bool:
if not isinstance(message, Mapping) or message.get("role") != "tool":
return False
if str(message.get("status") or "").lower() == "error":
return True
content = _message_content_text(message.get("content")).strip().lower()
return content.startswith("error:") or content.startswith("command blocked:")
def _strip_failed_artifact_write_attempts(
messages: list[Any],
pending: _PendingWriteFileBridge,
) -> list[Any]:
"""Remove failed same-target writer attempts from renderer source context.
A weak model may first emit metadata-only ``write_file`` and then place an
entire artifact in a failed ``bash`` heredoc. Replaying those calls into the
isolated renderer both wastes the context window and teaches it to repeat
the same orchestration mistake. Keep successful files and all unrelated
tool evidence; remove only failed ``write_file``/``bash`` pairs that target
the already prepared path.
"""
failed_results = {
str(message.get("tool_call_id") or "")
for message in messages
if _failed_tool_result(message)
}
failed_results.discard("")
if not failed_results:
return messages
removable_ids: set[str] = set()
for message in messages:
if not isinstance(message, Mapping) or message.get("role") != "assistant":
continue
tool_calls = message.get("tool_calls")
if not isinstance(tool_calls, list):
continue
for call in tool_calls:
if not isinstance(call, Mapping):
continue
call_id = str(call.get("id") or "")
if call_id not in failed_results:
continue
name = _payload_tool_name(call)
args, raw_args = _payload_tool_arguments(call)
if name == _WRITE_FILE_TOOL_NAME and str(args.get("path") or "") == pending.path:
removable_ids.add(call_id)
continue
if name == "bash":
command = args.get("command")
command_text = command if isinstance(command, str) else raw_args
if pending.path in command_text:
removable_ids.add(call_id)
if not removable_ids:
return messages
cleaned: list[Any] = []
for message in messages:
if not isinstance(message, Mapping):
cleaned.append(message)
continue
if (
message.get("role") == "tool"
and str(message.get("tool_call_id") or "") in removable_ids
):
continue
if message.get("role") != "assistant":
cleaned.append(message)
continue
tool_calls = message.get("tool_calls")
if not isinstance(tool_calls, list):
cleaned.append(message)
continue
visible_calls = [
call
for call in tool_calls
if not (
isinstance(call, Mapping)
and str(call.get("id") or "") in removable_ids
)
]
if not visible_calls and len(visible_calls) != len(tool_calls):
# Its preface/reasoning described the failed orchestration rather
# than artifact content; do not retain a misleading assistant turn.
continue
if len(visible_calls) != len(tool_calls):
rewritten = dict(message)
rewritten["tool_calls"] = visible_calls
cleaned.append(rewritten)
else:
cleaned.append(message)
return cleaned
def _artifact_renderer_messages(
messages: list[Any],
pending: _PendingWriteFileBridge,
*,
has_control_exchange: bool = True,
) -> list[Any]:
"""Build a renderer-only request without the lead-agent delivery prompt.
Non-system conversation turns and completed tool evidence remain available
as source material. The current ``begin_write_file`` assistant/tool pair
is transport control and is replaced by one server-owned renderer request;
in particular, any conversational preface on that assistant turn cannot
become file bytes or steer the renderer into another tool call. The server
owns the target metadata, while the provider's ordinary content channel is
the exact file-body channel.
"""
if has_control_exchange:
cleaned = _strip_older_begin_write_file_exchanges(messages)
historical = cleaned[:-2]
else:
# The buffered-gateway path never publishes or executes a preparation
# call. Reuse the original conversation directly while still removing
# any historical controls left by an older compatibility model.
historical = _strip_historical_begin_write_file_exchanges(messages)
historical_messages = _strip_failed_artifact_write_attempts(
historical,
pending,
)
source_messages = [
dict(message)
for message in historical_messages
if isinstance(message, Mapping) and message.get("role") != "system"
]
agent_system_context = [
_message_content_text(message.get("content"))
for message in historical_messages
if isinstance(message, Mapping)
and message.get("role") == "system"
and _message_content_text(message.get("content"))
]
renderer_system = _ARTIFACT_RENDERER_SYSTEM_INSTRUCTION
if agent_system_context:
# Preserve the content/style/safety contract of custom agents without
# restoring their orchestration instructions as active chat turns.
# The server-owned transaction remains the trust boundary; this context
# can improve artifact fidelity but cannot change the prepared path,
# append intent, tool catalogue, or reasoning/content channel mapping.
renderer_system = (
f"{renderer_system}\n\n"
"The following trusted original agent system context remains "
"applicable only to artifact content, style, and safety. Its "
"tool-selection, delivery, and user-facing response directions "
"are superseded by the renderer transaction above:\n"
+ json.dumps(
agent_system_context,
ensure_ascii=False,
separators=(",", ":"),
)
+ "\nThe server-owned file-body transaction remains controlling."
)
prepared = json.dumps(
{
"path": pending.path,
"description": pending.description,
"append": pending.append,
},
ensure_ascii=False,
separators=(",", ":"),
)
return [
{"role": "system", "content": renderer_system},
*source_messages,
{
"role": "user",
"content": (
"Render the prepared artifact now. The server owns the target "
f"metadata and will perform the final write: {prepared}"
),
},
]
def _has_pending_begin_exchange(
messages: list[Any],
pending: _PendingWriteFileBridge,
) -> bool:
"""Validate the immediately preceding internal prepare exchange.
The accepted assistant/tool pair is intentionally retained in the body
request. It is the causal hand-off that tells the provider the internal
prepare call succeeded and that the next response is the prepared body.
Dropping it leaves the lead-agent system prompt asking for ``write_file``
while the body request advertises no tools; compatible gateways may then
serialize a tool call as ordinary ``<write_file>{...}`` text.
"""
if len(messages) < 2:
return False
tool_result = messages[-1]
assistant = messages[-2]
if not isinstance(tool_result, dict) or tool_result.get("role") != "tool":
return False
if str(tool_result.get("tool_call_id") or "") != pending.begin_tool_call_id:
return False
if not isinstance(assistant, dict) or assistant.get("role") != "assistant":
return False
tool_calls = assistant.get("tool_calls")
if not isinstance(tool_calls, list) or len(tool_calls) != 1:
return False
call = tool_calls[0]
function = call.get("function") if isinstance(call, Mapping) else None
if (
not isinstance(call, Mapping)
or str(call.get("id") or "") != pending.begin_tool_call_id
or not isinstance(function, Mapping)
or function.get("name") != _BEGIN_WRITE_FILE_TOOL_NAME
):
return False
return True
def _strip_older_begin_write_file_exchanges(
messages: list[Any],
) -> list[Any]:
"""Hide historical bridge controls while preserving the current hand-off.
The last assistant/tool pair has already been validated by
:func:`_has_pending_begin_exchange`. Older bridge exchanges are transport
implementation details and must not accumulate in provider context.
"""
historical = _strip_historical_begin_write_file_exchanges(messages[:-2])
return [*historical, *messages[-2:]]
def _rewrite_write_file_tool_choice(payload: dict[str, Any]) -> None:
"""Map an explicitly forced writer to the bridge's prepare tool."""
choice = payload.get("tool_choice")
if choice == _WRITE_FILE_TOOL_NAME:
payload["tool_choice"] = _BEGIN_WRITE_FILE_TOOL_NAME
return
if not isinstance(choice, dict):
return
function = choice.get("function")
if isinstance(function, dict) and function.get("name") == _WRITE_FILE_TOOL_NAME:
rewritten_function = dict(function)
rewritten_function["name"] = _BEGIN_WRITE_FILE_TOOL_NAME
rewritten_choice = dict(choice)
rewritten_choice["function"] = rewritten_function
payload["tool_choice"] = rewritten_choice
def _rewrite_artifact_renderer_tool_choice(payload: dict[str, Any]) -> None:
"""Keep forced choices aligned with the renderer's provider-facing alias."""
choice = payload.get("tool_choice")
if choice == _BEGIN_WRITE_FILE_TOOL_NAME:
payload["tool_choice"] = _WRITE_FILE_TOOL_NAME
return
if not isinstance(choice, dict):
return
function = choice.get("function")
if isinstance(function, dict) and function.get("name") == _BEGIN_WRITE_FILE_TOOL_NAME:
rewritten_function = dict(function)
rewritten_function["name"] = _WRITE_FILE_TOOL_NAME
rewritten_choice = dict(choice)
rewritten_choice["function"] = rewritten_function
payload["tool_choice"] = rewritten_choice
def _configure_write_file_bridge_payload(
payload: dict[str, Any],
original_messages: list[Any],
state: _WriteFileBridgeStreamState,
) -> None:
"""Apply the model-scoped compatibility transport to one provider request."""
if state.transport not in _WRITE_FILE_BRIDGE_TRANSPORTS:
# ``begin_write_file`` is an internal compatibility control tool. Even
# if a deployment accidentally registers it globally, native models see
# the same tool catalogue they had before this feature existed.
_hide_begin_write_file_for_native_request(payload)
return
payload_messages = payload.get("messages")
if not isinstance(payload_messages, list):
state.pending = None
# Responses-style payloads use a different input container. This
# bridge targets chat-completions messages, so degrade safely to the
# native writer and never expose an unusable handshake.
_hide_begin_write_file_for_native_request(payload)
return
tools = payload.get("tools")
tool_names = {_payload_tool_name(tool) for tool in tools} if isinstance(tools, list) else set()
if state.transport in _ARTIFACT_BODY_TRANSPORTS:
state.renderer_html_references = {
path: content
for path, content in _successful_read_file_contents(
original_messages
).items()
if _is_complete_html_document(content)
}
direct_body_request = (
state.transport == "buffered_gateway"
and state.buffered_gateway_body_request
and state.pending is not None
)
pending = (
state.pending
if direct_body_request
else _pending_write_file_bridge(original_messages)
)
if pending is not None and (
direct_body_request
or _has_pending_begin_exchange(payload_messages, pending)
):
state.pending = pending
state.inline_think_filter = InlineThinkTagFilter(
allow_implicit_close=pending.allow_implicit_think_close,
leading_only=True,
)
state.markdown_fence_projection = (
_MarkdownFenceProjection()
if state.transport in _ARTIFACT_BODY_TRANSPORTS
and not pending.append
and re.search(r"\.(?:md|markdown)\Z", pending.path, re.IGNORECASE)
else None
)
if state.transport in _ARTIFACT_BODY_TRANSPORTS:
state.renderer_html_reference = state.renderer_html_references.get(
pending.path
)
payload["messages"] = _artifact_renderer_messages(
payload_messages,
pending,
has_control_exchange=not direct_body_request,
)
else:
payload_messages = _strip_older_begin_write_file_exchanges(
payload_messages
)
payload["messages"] = payload_messages
prepared = json.dumps(
{
"path": pending.path,
"description": pending.description,
"append": pending.append,
},
ensure_ascii=False,
separators=(",", ":"),
)
_append_system_instruction(
payload_messages,
f"{_TEXT_BRIDGE_BODY_INSTRUCTION}\nPrepared target: {prepared}",
)
# The graph still owns and executes the canonical write_file assembled
# from the content stream. Artifact renderers retain only bounded
# prerequisite tools, so a model can defer instead of hallucinating a
# read call into an otherwise tool-less body round. Mutation, shell and
# writer tools remain unavailable. Legacy text_bridge stays text-only.
prerequisite_tools = (
[
tool
for tool in tools
if _payload_tool_name(tool)
in _ARTIFACT_RENDERER_PREREQUISITE_TOOLS
]
if state.transport in _ARTIFACT_BODY_TRANSPORTS
and isinstance(tools, list)
else []
)
state.renderer_prerequisite_tools = frozenset(
_payload_tool_name(tool) for tool in prerequisite_tools
)
if prerequisite_tools:
payload["tools"] = prerequisite_tools
payload["parallel_tool_calls"] = False
else:
payload.pop("tools", None)
payload.pop("parallel_tool_calls", None)
for key in ("tool_choice", "functions", "function_call"):
payload.pop(key, None)
return
state.pending = None
historical_messages = _strip_historical_begin_write_file_exchanges(
payload_messages
)
if historical_messages is not payload_messages:
payload["messages"] = historical_messages
payload_messages = historical_messages
if not isinstance(tools, list):
return
if state.transport == "buffered_gateway":
if _WRITE_FILE_TOOL_NAME not in tool_names:
# No writer means there is nothing to adapt. Keep every other tool
# untouched and ensure a stale internal control cannot leak out.
_hide_begin_write_file_for_native_request(payload)
return
state.selection_write_file_alias = True
payload["tools"] = [
_buffered_gateway_selection_tool(tool)
for tool in tools
if _payload_tool_name(tool) != _BEGIN_WRITE_FILE_TOOL_NAME
]
payload["parallel_tool_calls"] = False
_rewrite_artifact_renderer_tool_choice(payload)
_append_system_instruction(
payload_messages,
_BUFFERED_GATEWAY_SELECTION_INSTRUCTION,
)
return
if not {
_WRITE_FILE_TOOL_NAME,
_BEGIN_WRITE_FILE_TOOL_NAME,
}.issubset(tool_names):
# A partial registration cannot complete the handshake. Preserve the
# original native writer and hide the internal companion.
_hide_begin_write_file_for_native_request(payload)
return
state.selection_write_file_alias = state.transport == "artifact_renderer"
payload["tools"] = [
_selection_tool(
tool,
provider_name=(
_WRITE_FILE_TOOL_NAME
if state.selection_write_file_alias
else None
),
)
for tool in tools
if _payload_tool_name(tool) != _WRITE_FILE_TOOL_NAME
]
if state.selection_write_file_alias:
payload["parallel_tool_calls"] = False
_rewrite_artifact_renderer_tool_choice(payload)
else:
_rewrite_write_file_tool_choice(payload)
_append_system_instruction(
payload_messages,
_ARTIFACT_RENDERER_SELECTION_INSTRUCTION
if state.transport == "artifact_renderer"
else _TEXT_BRIDGE_SELECTION_INSTRUCTION,
)
def _json_string_fragment(value: str) -> str:
return json.dumps(value, ensure_ascii=False)[1:-1]
def _compatible_boolean(value: Any) -> bool | None:
"""Normalize only booleans and their canonical JSON text spellings."""
if isinstance(value, bool):
return value
if isinstance(value, str) and value.strip().lower() in {"true", "false"}:
return value.strip().lower() == "true"
return None
def _rewrite_selection_write_file_alias(
chunk: dict[str, Any],
state: _WriteFileBridgeStreamState,
) -> None:
"""Turn the provider-facing alias into the metadata-only prepare control.
Some gateways ignore the advertised schema and start generating a large
``content`` property anyway. Keeping that direct writer as a fallback
recreates the original problem: the frontend receives no canonical
``write_file`` until the whole JSON argument has finished. As soon as all
metadata is complete, synthesize a terminal ``begin_write_file`` and close
this upstream stream. LangGraph then runs the normal isolated body round,
whose text is projected incrementally as canonical ``write_file`` chunks.
If the gateway returns the whole call atomically, its content is discarded
at the real terminal frame and only the prepare metadata is exposed. This
keeps one invariant for every opted-in renderer model: file bytes always
travel through the streamable body round, never through a hidden bypass.
"""
if (
state.transport not in _ARTIFACT_BODY_TRANSPORTS
or state.pending is not None
or not state.selection_write_file_alias
):
return
for choice in _stream_choices(chunk):
delta = choice.get("delta")
if not isinstance(delta, dict):
continue
if _extract_reasoning_content(delta):
state.selection_alias_has_native_reasoning = True
selection_text = _message_content_text(delta.get("content"))
if selection_text:
probe = (
f"{state.selection_alias_think_probe_suffix}{selection_text}"
).lower()
open_at = probe.find("<think>")
close_at = probe.find("</think>")
if open_at >= 0 and (close_at < 0 or open_at < close_at):
state.selection_alias_saw_think_open = True
if close_at >= 0 and not state.selection_alias_saw_think_open:
state.selection_alias_implicit_think_close = True
state.selection_alias_think_probe_suffix = probe[-8:]
tool_calls = delta.get("tool_calls")
visible_calls: list[dict[str, Any]] = []
saw_unrelated_call = False
if isinstance(tool_calls, list):
for position, tool_call in enumerate(tool_calls):
if not isinstance(tool_call, dict):
continue
try:
index = int(tool_call.get("index", position))
except (TypeError, ValueError):
index = position
function = tool_call.get("function")
if not isinstance(function, dict):
visible_calls.append(tool_call)
saw_unrelated_call = True
continue
name_fragment = function.get("name")
name_fragment = (
name_fragment if isinstance(name_fragment, str) else ""
)
buffered = state.selection_alias_calls.get(index)
if buffered is None:
if not name_fragment or not _WRITE_FILE_TOOL_NAME.startswith(
name_fragment
):
visible_calls.append(tool_call)
saw_unrelated_call = True
continue
buffered = _SelectionAliasCallState(index=index)
state.selection_alias_calls[index] = buffered
call_id = str(tool_call.get("id") or "")
if call_id and not buffered.call_id:
buffered.call_id = call_id
call_type = str(tool_call.get("type") or "")
if call_type:
buffered.call_type = call_type
buffered.name = _merge_compatible_fragment(
buffered.name,
name_fragment,
)
if not _WRITE_FILE_TOOL_NAME.startswith(buffered.name):
raise RuntimeError(
"Artifact renderer returned a malformed write_file alias"
)
arguments_fragment = function.get("arguments")
arguments_fragment = (
arguments_fragment
if isinstance(arguments_fragment, str)
else ""
)
buffered.arguments = _merge_compatible_fragment(
buffered.arguments,
arguments_fragment,
)
if len(state.selection_alias_calls) > 1:
raise RuntimeError(
"Artifact renderer emitted more than one write_file call"
)
if state.selection_alias_calls and (saw_unrelated_call or visible_calls):
raise RuntimeError(
"Artifact renderer emitted write_file in parallel with another tool"
)
if visible_calls:
delta["tool_calls"] = visible_calls
else:
delta.pop("tool_calls", None)
finish_reason = str(choice.get("finish_reason") or "")
if not finish_reason and len(state.selection_alias_calls) == 1:
buffered = next(iter(state.selection_alias_calls.values()))
metadata = (
_complete_selection_alias_metadata(buffered.arguments)
if buffered.name == _WRITE_FILE_TOOL_NAME and buffered.call_id
else None
)
if metadata is not None:
try:
_content, content_found, content_complete = (
_partial_top_level_json_string(
buffered.arguments,
"content",
)
)
except ValueError:
content_found = content_complete = False
# A complete content value in the same frame has already paid
# the generation cost. Let the terminal path preserve any
# leading reasoning before it discards those bytes. An absent
# or still-open content value is the useful cancellation point.
if not (content_found and content_complete):
buffered.decision = "begin"
buffered.emitted = True
if state.transport == "buffered_gateway":
state.pending = _pending_from_buffered_gateway_selection(
buffered,
metadata,
allow_implicit_think_close=(
state.selection_alias_implicit_think_close
and not state.selection_alias_has_native_reasoning
),
)
# Keep this as one logical assistant stream. The
# adapter closes the buffered tool stream and starts
# the ordinary-text body request before yielding a
# terminal frame to LangGraph.
delta.pop("tool_calls", None)
choice["finish_reason"] = None
else:
delta["tool_calls"] = [
{
"index": buffered.index,
"id": buffered.call_id,
"type": buffered.call_type,
"function": {
"name": _BEGIN_WRITE_FILE_TOOL_NAME,
"arguments": json.dumps(
metadata,
ensure_ascii=False,
separators=(",", ":"),
),
},
}
]
choice["finish_reason"] = "tool_calls"
state.selection_alias_early_stop = True
diagnostics = _ACTIVE_TOOL_STREAM.get()
if diagnostics is not None:
diagnostics.finish_reason = (
"none"
if state.transport == "buffered_gateway"
else "tool_calls"
)
diagnostics.intentional_early_finish = True
return
if not finish_reason:
continue
if state.selection_alias_calls and finish_reason not in {
"tool_calls",
"function_call",
"stop",
"end_turn",
}:
state.aborted = True
raise ArtifactRendererTransientProtocolError(
"Artifact renderer did not complete the write_file alias"
)
terminal_calls: list[dict[str, Any]] = list(
delta.get("tool_calls") or []
)
for index, buffered in sorted(state.selection_alias_calls.items()):
if buffered.emitted:
continue
if buffered.name != _WRITE_FILE_TOOL_NAME:
raise ArtifactRendererTransientProtocolError(
"Artifact renderer returned an incomplete write_file alias"
)
terminal_arguments = buffered.arguments
try:
parsed_arguments = json.loads(terminal_arguments)
except (TypeError, ValueError) as exc:
raise ArtifactRendererTransientProtocolError(
"Artifact renderer returned invalid write_file arguments"
) from exc
if not isinstance(parsed_arguments, Mapping):
raise RuntimeError(
"Artifact renderer returned invalid write_file arguments"
)
path_value = parsed_arguments.get("path")
description_value = parsed_arguments.get("description")
path = path_value.strip() if isinstance(path_value, str) else ""
description = (
description_value.strip()
if isinstance(description_value, str)
else ""
)
append = _compatible_boolean(parsed_arguments.get("append", False))
if not path.startswith("/") or append is None:
raise RuntimeError(
"Artifact renderer returned invalid write_file arguments"
)
content = parsed_arguments.get("content")
if "content" in parsed_arguments:
if not isinstance(content, str):
raise RuntimeError(
"Artifact renderer returned invalid write_file arguments"
)
think_filter = InlineThinkTagFilter(leading_only=True)
if state.selection_alias_has_native_reasoning:
think_filter.confirm_visible_channel()
_visible, inline_reasoning = think_filter.push_parts(content)
_tail_visible, tail_reasoning = think_filter.finish_parts()
_append_bridge_reasoning_delta(
delta,
f"{inline_reasoning}{tail_reasoning}",
)
buffered.decision = "begin"
metadata = {
"description": description or "Write the prepared file",
"path": path,
"append": append,
}
if state.transport == "buffered_gateway":
state.pending = _pending_from_buffered_gateway_selection(
buffered,
metadata,
allow_implicit_think_close=(
state.selection_alias_implicit_think_close
and not state.selection_alias_has_native_reasoning
),
)
choice["finish_reason"] = None
state.selection_alias_early_stop = True
else:
terminal_arguments = json.dumps(
metadata,
ensure_ascii=False,
separators=(",", ":"),
)
terminal_calls.append(
{
"index": index,
"id": buffered.call_id,
"type": buffered.call_type,
"function": {
"name": _BEGIN_WRITE_FILE_TOOL_NAME,
"arguments": terminal_arguments,
},
}
)
buffered.emitted = True
if terminal_calls:
delta["tool_calls"] = terminal_calls
def _append_bridge_reasoning_delta(delta: dict[str, Any], reasoning: str) -> None:
"""Route inline provider thinking through the normal reasoning channel.
The bridge operates before LangChain converts a raw provider chunk. Putting
the separated trace on ``reasoning_content`` lets the existing converter
and frontend render it exactly like native provider reasoning, while the
synthetic tool arguments receive only confirmed file bytes.
"""
if not reasoning:
return
existing = _extract_reasoning_content(delta)
if existing:
delta["reasoning_content"] = (
existing if reasoning in existing else f"{existing}{reasoning}"
)
else:
delta["reasoning_content"] = reasoning
def _write_file_argument_prefix(pending: _PendingWriteFileBridge) -> str:
metadata = json.dumps(
{
"description": pending.description,
"path": pending.path,
# LangChain best-effort parses unfinished JSON. Keep every
# intermediate call schema-invalid; the normal finishing frame
# appends a duplicate key with the boolean commit value (JSON
# last-key-wins).
"append": _WRITE_FILE_BRIDGE_INCOMPLETE,
},
ensure_ascii=False,
separators=(",", ":"),
)
return f'{metadata[:-1]},"content":"'
def _synthetic_write_tool_delta(
pending: _PendingWriteFileBridge,
arguments: str,
*,
first: bool,
) -> dict[str, Any]:
function: dict[str, Any] = {"arguments": arguments}
tool_call: dict[str, Any] = {"index": 0, "function": function}
if first:
tool_call.update({"id": pending.write_tool_call_id, "type": "function"})
function["name"] = _WRITE_FILE_TOOL_NAME
return tool_call
def _merge_compatible_fragment(previous: str, current: str) -> str:
"""Merge either a true delta or a growing compatible-gateway snapshot."""
if not current:
return previous
if not previous:
return current
if current == previous:
return previous
if current.startswith(previous):
return current
return f"{previous}{current}"
def _capture_renderer_fallback_write(
tool_calls: Any,
state: _WriteFileBridgeStreamState,
) -> str:
"""Capture one provider-authored writer call inside a renderer body turn.
The ordinary content channel remains the preferred streaming path. This is
a compatibility fallback for weak models that ignore the no-tools renderer
contract and emit ``write_file`` anyway. The cumulative argument buffer is
retained for terminal validation against the server-owned target, while a
separate parser may project only the top-level ``content`` string into the
canonical synthetic writer stream. No sandbox write executes before the
complete call passes validation.
"""
if not isinstance(tool_calls, list) or len(tool_calls) != 1:
raise RuntimeError(
"Artifact renderer emitted an unexpected provider tool call"
)
tool_call = tool_calls[0]
if not isinstance(tool_call, Mapping):
raise RuntimeError(
"Artifact renderer emitted an unexpected provider tool call"
)
try:
index = int(tool_call.get("index", 0))
except (TypeError, ValueError):
index = 0
call_id = str(tool_call.get("id") or "")
function = tool_call.get("function")
if not isinstance(function, Mapping):
raise RuntimeError(
"Artifact renderer emitted an unexpected provider tool call"
)
name_fragment = function.get("name")
name_fragment = name_fragment if isinstance(name_fragment, str) else ""
arguments_fragment = function.get("arguments")
arguments_fragment = (
arguments_fragment if isinstance(arguments_fragment, str) else ""
)
if not state.renderer_fallback_active:
if not name_fragment or not _WRITE_FILE_TOOL_NAME.startswith(name_fragment):
raise RuntimeError(
"Artifact renderer emitted an unexpected provider tool call"
)
state.renderer_fallback_active = True
state.renderer_fallback_index = index
state.renderer_fallback_call_id = call_id
elif state.renderer_fallback_index != index:
raise RuntimeError(
"Artifact renderer emitted an unexpected provider tool call"
)
elif (
call_id
and state.renderer_fallback_call_id
and call_id != state.renderer_fallback_call_id
):
raise RuntimeError(
"Artifact renderer emitted an unexpected provider tool call"
)
elif call_id and not state.renderer_fallback_call_id:
state.renderer_fallback_call_id = call_id
state.renderer_fallback_name = _merge_compatible_fragment(
state.renderer_fallback_name,
name_fragment,
)
if not _WRITE_FILE_TOOL_NAME.startswith(state.renderer_fallback_name):
raise RuntimeError(
"Artifact renderer emitted an unexpected provider tool call"
)
new_arguments = state.renderer_fallback_arguments.merge(arguments_fragment)
try:
return state.renderer_fallback_content.feed(new_arguments)
except ValueError as exc:
raise ArtifactRendererTransientProtocolError(
"Artifact renderer returned invalid write_file arguments"
) from exc
def _validated_renderer_fallback_content(
state: _WriteFileBridgeStreamState,
pending: _PendingWriteFileBridge,
) -> str:
if state.renderer_fallback_name != _WRITE_FILE_TOOL_NAME:
raise ArtifactRendererTransientProtocolError(
"Artifact renderer returned an incomplete write_file call"
)
try:
raw_arguments = state.renderer_fallback_arguments.materialize()
arguments = json.loads(raw_arguments)
except (TypeError, ValueError) as exc:
raise ArtifactRendererTransientProtocolError(
"Artifact renderer returned invalid write_file arguments"
) from exc
if not isinstance(arguments, Mapping):
raise RuntimeError("Artifact renderer returned invalid write_file arguments")
if str(arguments.get("path") or "") != pending.path:
raise RuntimeError("Artifact renderer attempted a different file path")
# Several OpenAI-compatible tool parsers preserve a JSON-schema boolean as
# its textual spelling. Arbitrary truthy strings remain a protocol error.
append = _compatible_boolean(arguments.get("append", pending.append))
if append is None or append != pending.append:
raise RuntimeError("Artifact renderer changed the prepared append intent")
content = arguments.get("content")
if not isinstance(content, str) or not content:
raise RuntimeError("Artifact renderer returned no file body")
try:
streamed_content = state.renderer_fallback_content.content()
except ValueError as exc:
raise ArtifactRendererTransientProtocolError(
"Artifact renderer returned invalid write_file arguments"
) from exc
if (
not state.renderer_fallback_content.complete
or streamed_content != content
):
raise ArtifactRendererTransientProtocolError(
"Artifact renderer returned an incomplete write_file call"
)
return content
def _capture_renderer_prerequisite_calls(
tool_calls: Any,
state: _WriteFileBridgeStreamState,
) -> bool:
"""Return true when every provider call is an advertised prerequisite."""
if not isinstance(tool_calls, list) or not tool_calls:
return False
if len(tool_calls) != 1:
raise RuntimeError(
"Artifact renderer emitted parallel prerequisite tool calls"
)
captured: dict[int, str] = dict(state.renderer_deferred_tool_names)
for position, tool_call in enumerate(tool_calls):
if not isinstance(tool_call, Mapping):
return False
try:
index = int(tool_call.get("index", position))
except (TypeError, ValueError):
index = position
if captured and index not in captured:
raise RuntimeError(
"Artifact renderer emitted parallel prerequisite tool calls"
)
function = tool_call.get("function")
if not isinstance(function, Mapping):
return False
fragment = function.get("name")
fragment = fragment if isinstance(fragment, str) else ""
previous = captured.get(index, "")
candidate = _merge_compatible_fragment(previous, fragment)
if not candidate:
return False
if not any(
allowed.startswith(candidate)
for allowed in state.renderer_prerequisite_tools
):
return False
captured[index] = candidate
state.renderer_deferred_tool_names = captured
state.renderer_deferred = True
return True
def _route_renderer_deferred_content(
delta: dict[str, Any],
state: _WriteFileBridgeStreamState,
) -> None:
"""Keep reasoning visible but reject body bytes mixed with a deferred tool."""
control_text = _message_content_text(delta.get("content"))
if control_text:
if _extract_reasoning_content(delta):
state.inline_think_filter.confirm_visible_channel()
visible, inline_reasoning = state.inline_think_filter.push_parts(control_text)
_append_bridge_reasoning_delta(delta, inline_reasoning)
if visible.strip():
state.aborted = True
raise RuntimeError(
"Artifact renderer mixed file content with a prerequisite tool call"
)
state.renderer_leading_whitespace = ""
delta["content"] = ""
def _bridge_streamed_file_body(chunk: dict[str, Any], state: _WriteFileBridgeStreamState) -> None:
"""Convert one prepared ordinary-text response into write_file deltas.
The explicit ``begin_write_file`` receipt is the deterministic boundary:
only the immediately following invocation enters this path. One optional
leading inline ``<think>`` envelope is routed to the normal reasoning
channel; provider-native reasoning stays on that channel as well. The first
ordinary content byte becomes a standard tool-call chunk immediately,
matching native sandbox preview behavior without asking the model to obey a
second JSON or delimiter protocol.
"""
pending = state.pending
if pending is None:
return
choices = _stream_choices(chunk)
if not choices:
return
if state.closed or state.aborted:
# A conforming provider emits no more body/tool bytes after its terminal
# frame (usage-only frames are fine). A previously yielded valid tool
# chunk cannot be retracted safely by appending malformed arguments:
# LangChain may retain the earlier parsed call. Abort the whole model
# node instead, so LangGraph never advances to ToolNode/sandbox IO.
trailing_payload = False
for trailing_choice in choices:
trailing_delta = trailing_choice.get("delta")
if isinstance(trailing_delta, dict):
trailing_payload = trailing_payload or bool(
_message_content_text(trailing_delta.get("content"))
or trailing_delta.get("tool_calls")
)
trailing_delta["content"] = ""
trailing_delta.pop("tool_calls", None)
if trailing_payload and state.closed and not state.aborted:
state.aborted = True
logger.warning(
"ARTIFACT_RENDERER_PROTOCOL_REJECTED "
"reason=post_terminal_payload finish_reason=tool_calls"
)
raise RuntimeError(
"Artifact renderer protocol rejected after terminal frame"
)
return
choice = choices[0]
delta = choice.get("delta")
if not isinstance(delta, dict):
delta = {}
choice["delta"] = delta
existing_tool_calls = delta.get("tool_calls")
fallback_file_text = ""
if existing_tool_calls and state.transport in _ARTIFACT_BODY_TRANSPORTS:
if _capture_renderer_prerequisite_calls(existing_tool_calls, state):
if state.started or state.renderer_fallback_active:
state.aborted = True
raise RuntimeError(
"Artifact renderer requested a prerequisite after file bytes began"
)
else:
fallback_was_active = state.renderer_fallback_active
if not fallback_was_active and state.started:
state.aborted = True
raise ArtifactRendererTransientProtocolError(
"Artifact renderer switched to write_file after file bytes began"
)
fallback_file_text = _capture_renderer_fallback_write(
existing_tool_calls,
state,
)
if not fallback_was_active:
# A provider may emit harmless whitespace alongside native
# reasoning before switching to a fallback tool call. None of
# that speculative ordinary-content state belongs to the
# tool's content value; restart only the file-body projectors.
state.inline_think_filter = InlineThinkTagFilter(
allow_implicit_close=pending.allow_implicit_think_close,
leading_only=True,
)
state.markdown_fence_projection = (
_MarkdownFenceProjection()
if not pending.append and re.search(
r"\.(?:md|markdown)\Z",
pending.path,
re.IGNORECASE,
)
else None
)
state.renderer_leading_whitespace = ""
state.renderer_html_probe = ""
state.renderer_html_probe_resolved = False
state.renderer_html_recovered = False
state.renderer_html_suspected_truncated = False
state.renderer_html_content.clear()
delta.pop("tool_calls", None)
if state.renderer_deferred:
_route_renderer_deferred_content(delta, state)
finish_reason = str(choice.get("finish_reason") or "")
if finish_reason:
if finish_reason not in {"tool_calls", "function_call"}:
state.aborted = True
raise RuntimeError(
"Artifact renderer did not complete its prerequisite tool call"
)
tail_visible, tail_reasoning = state.inline_think_filter.finish_parts()
_append_bridge_reasoning_delta(delta, tail_reasoning)
if tail_visible.strip():
state.aborted = True
raise RuntimeError(
"Artifact renderer mixed file content with a prerequisite tool call"
)
names = tuple(state.renderer_deferred_tool_names.values())
if (
len(names) != 1
or names[0] not in state.renderer_prerequisite_tools
):
state.aborted = True
raise RuntimeError(
"Artifact renderer returned an incomplete prerequisite tool call"
)
state.closed = True
return
if state.renderer_fallback_active:
# Preserve provider/inline reasoning for the message list. Extract
# only the fallback call's top-level content string and immediately
# project newly decoded bytes into the server-owned canonical writer.
# Provider path/append bytes are never forwarded to LangGraph.
control_text = _message_content_text(delta.get("content"))
if control_text:
control_visible, inline_reasoning = (
state.renderer_fallback_control_filter.push_parts(control_text)
)
_append_bridge_reasoning_delta(
delta,
f"{inline_reasoning}{control_visible}",
)
delta["content"] = ""
arguments = ""
first = not state.started
file_text = fallback_file_text
if _extract_reasoning_content(delta):
confirmed = state.inline_think_filter.confirm_visible_channel()
if confirmed:
file_text = f"{confirmed}{file_text}"
if file_text:
visible_text, inline_reasoning = state.inline_think_filter.push_parts(
file_text
)
_append_bridge_reasoning_delta(delta, inline_reasoning)
if state.markdown_fence_projection is not None:
visible_text = state.markdown_fence_projection.push(visible_text)
visible_text = _project_renderer_visible_text(
state,
pending,
visible_text,
)
if visible_text:
if first:
arguments += _write_file_argument_prefix(pending)
arguments += _json_string_fragment(visible_text)
state.started = True
finish_reason = str(choice.get("finish_reason") or "")
if finish_reason:
if finish_reason not in {
"stop",
"end_turn",
"tool_calls",
"function_call",
}:
state.aborted = True
raise RuntimeError(
"Artifact renderer did not complete the fallback write_file call"
)
_validated_renderer_fallback_content(state, pending)
control_visible, control_reasoning = (
state.renderer_fallback_control_filter.finish_parts()
)
_append_bridge_reasoning_delta(
delta,
f"{control_reasoning}{control_visible}",
)
unclosed_thinking = state.inline_think_filter.has_unclosed_thinking
tail_visible, tail_reasoning = state.inline_think_filter.finish_parts()
_append_bridge_reasoning_delta(delta, tail_reasoning)
if unclosed_thinking:
state.aborted = True
logger.warning(
"ARTIFACT_RENDERER_PROTOCOL_REJECTED "
"reason=unclosed_reasoning finish_reason=%s",
_safe_finish_reason(finish_reason),
)
raise RuntimeError(
"Artifact renderer ended inside a reasoning envelope"
)
replacement_content: str | None = None
if state.markdown_fence_projection is not None:
projected_tail = state.markdown_fence_projection.push(tail_visible)
final_tail, replacement_content = (
state.markdown_fence_projection.finish()
)
tail_visible = f"{projected_tail}{final_tail}"
tail_visible = _project_renderer_visible_text(
state,
pending,
tail_visible,
final=True,
)
if tail_visible:
if not state.started:
first = True
arguments += _write_file_argument_prefix(pending)
arguments += _json_string_fragment(tail_visible)
state.started = True
if replacement_content is not None:
if not state.started:
first = True
arguments += _write_file_argument_prefix(pending)
else:
arguments += '","content":"'
arguments += _json_string_fragment(replacement_content)
state.started = True
if not state.started:
state.aborted = True
logger.warning(
"ARTIFACT_RENDERER_PROTOCOL_REJECTED "
"reason=empty_body finish_reason=%s",
_safe_finish_reason(finish_reason),
)
raise RuntimeError("Artifact renderer returned no file body")
_validate_renderer_artifact_complete(state, pending)
arguments += f'","append":{str(pending.append).lower()}}}'
state.closed = True
state.renderer_fallback_active = False
choice["finish_reason"] = "tool_calls"
if arguments:
delta["tool_calls"] = [
_synthetic_write_tool_delta(
pending,
arguments,
first=first,
)
]
return
if existing_tool_calls:
# The body request advertises no tools. Treat an unexpected provider
# tool call as a protocol failure, suppress it, and never let ToolNode
# execute a hallucinated operation.
delta["content"] = ""
delta.pop("tool_calls", None)
state.aborted = True
if state.transport in _ARTIFACT_BODY_TRANSPORTS:
raise RuntimeError(
"Artifact renderer emitted an unexpected provider tool call"
)
return
content = delta.get("content")
text = _message_content_text(content)
arguments = ""
first = not state.started
# A provider-native reasoning field is authoritative: ordinary content is
# already the answer/file channel, so stop probing it for inline tags. This
# also preserves a file that legitimately starts with literal <think> text.
if _extract_reasoning_content(delta):
confirmed = state.inline_think_filter.confirm_visible_channel()
if confirmed:
text = f"{confirmed}{text}"
if text:
visible_text, inline_reasoning = state.inline_think_filter.push_parts(text)
_append_bridge_reasoning_delta(delta, inline_reasoning)
if state.markdown_fence_projection is not None:
visible_text = state.markdown_fence_projection.push(visible_text)
if state.transport in _ARTIFACT_BODY_TRANSPORTS:
visible_text = _project_renderer_visible_text(
state,
pending,
visible_text,
)
# File bytes are represented only as synthetic tool arguments.
delta["content"] = ""
if visible_text:
if first:
arguments += _write_file_argument_prefix(pending)
arguments += _json_string_fragment(visible_text)
state.started = True
finish_reason = str(choice.get("finish_reason") or "")
if finish_reason:
if finish_reason in {"stop", "end_turn"}:
unclosed_thinking = state.inline_think_filter.has_unclosed_thinking
tail_visible, tail_reasoning = state.inline_think_filter.finish_parts()
_append_bridge_reasoning_delta(delta, tail_reasoning)
if unclosed_thinking:
# A provider that never leaves its reasoning envelope has not
# delivered any confirmed file body. Do not manufacture a
# successful write from private thought text.
state.aborted = True
if state.transport in _ARTIFACT_BODY_TRANSPORTS:
logger.warning(
"ARTIFACT_RENDERER_PROTOCOL_REJECTED "
"reason=unclosed_reasoning finish_reason=%s",
_safe_finish_reason(finish_reason),
)
raise RuntimeError(
"Artifact renderer ended inside a reasoning envelope"
)
else:
replacement_content: str | None = None
if state.markdown_fence_projection is not None:
projected_tail = state.markdown_fence_projection.push(
tail_visible
)
final_tail, replacement_content = (
state.markdown_fence_projection.finish()
)
tail_visible = f"{projected_tail}{final_tail}"
if state.transport in _ARTIFACT_BODY_TRANSPORTS:
tail_visible = _project_renderer_visible_text(
state,
pending,
tail_visible,
final=True,
)
if tail_visible:
if not state.started:
first = True
arguments += _write_file_argument_prefix(pending)
arguments += _json_string_fragment(tail_visible)
state.started = True
if replacement_content is not None:
if not state.started:
first = True
arguments += _write_file_argument_prefix(pending)
else:
# The first content value was a speculative streaming
# projection. A duplicate key repairs an ambiguous
# leading fence before JSON validation/tool execution;
# parsers use the final value, just as for ``append``.
arguments += '","content":"'
arguments += _json_string_fragment(replacement_content)
state.started = True
if not state.started:
if state.transport in _ARTIFACT_BODY_TRANSPORTS:
state.aborted = True
logger.warning(
"ARTIFACT_RENDERER_PROTOCOL_REJECTED "
"reason=empty_body finish_reason=%s",
_safe_finish_reason(finish_reason),
)
raise RuntimeError(
"Artifact renderer returned no file body"
)
# Legacy text_bridge retains its existing validation path.
arguments += _write_file_argument_prefix(pending)
state.started = True
first = True
if state.transport in _ARTIFACT_BODY_TRANSPORTS:
_validate_renderer_artifact_complete(state, pending)
arguments += f'","append":{str(pending.append).lower()}}}'
state.closed = True
choice["finish_reason"] = "tool_calls"
else:
# length/content_filter/error/tool_calls: the accumulated call
# retains its invalid commit sentinel, so ToolNode validation
# stops it before any file IO.
state.aborted = True
if state.transport in _ARTIFACT_BODY_TRANSPORTS:
logger.warning(
"ARTIFACT_RENDERER_PROTOCOL_REJECTED "
"reason=unsafe_finish finish_reason=%s",
_safe_finish_reason(finish_reason),
)
raise RuntimeError(
"Artifact renderer did not complete the file body"
)
if arguments:
delta["tool_calls"] = [
_synthetic_write_tool_delta(
pending,
arguments,
first=first,
)
]
def _ensure_write_file_bridge_finished(state: _WriteFileBridgeStreamState) -> None:
"""Reject a body stream that ended without an explicit safe finish frame."""
if state.transport not in _ARTIFACT_BODY_TRANSPORTS or state.aborted:
return
incomplete_alias = any(
not call.emitted for call in state.selection_alias_calls.values()
)
if state.pending is None and not incomplete_alias:
return
if state.pending is not None and state.closed:
return
state.aborted = True
logger.warning(
"ARTIFACT_RENDERER_PROTOCOL_REJECTED "
"reason=missing_terminal_frame finish_reason=none"
)
raise ArtifactRendererTransientProtocolError(
"Artifact renderer stream ended without a terminal frame"
)
def _log_stream_cleanup_failure(error: BaseException, *, asynchronous: bool) -> None:
try:
logger.debug(
"OpenAI-compatible %s stream cleanup failed error_type=%s",
"async" if asynchronous else "sync",
_safe_metric_name(type(error).__name__, fallback="other"),
)
except Exception:
# Preserve the stream/cleanup exception even if logging itself fails.
pass
class OpenAICompatibleUpstreamError(RuntimeError):
"""An OpenAI-compatible gateway reported an error inside a HTTP 200 body.
Several compatible gateways do not use an HTTP error status for quota,
concurrency, or inference failures. Instead they return ``choices=null``
(or an empty streamed chunk) together with a vendor ``base_resp`` object.
LangChain raises a generic ``TypeError`` for the non-streaming shape and
treats the streaming shape as an empty successful response. Raising one
stable exception here lets DeerFlow's model middleware retry both paths.
"""
def __init__(self, *, code: Any, message: str | None = None) -> None:
self.code = code
# Retain bounded provider text only for internal retry classification.
# Exception strings often reach framework logs implicitly, so never put
# the upstream response body there.
self._classification_detail = _bounded_error_message(message)
display_code = _safe_upstream_code(code)
super().__init__(f"OpenAI-compatible upstream application error (code={display_code})")
def _bounded_error_message(value: Any, *, limit: int = 500) -> str:
"""Return a bounded one-line provider message for in-process classification."""
if not isinstance(value, str):
return ""
normalized = " ".join(value.split())
return normalized[:limit]
def _application_error(payload: Mapping[str, Any]) -> tuple[Any, str | None] | None:
"""Extract a non-success ``base_resp`` error from a compatible payload."""
top_level_error = payload.get("error")
# Compatible gateways may emit an empty error object on successful SSE
# frames. The SDK treats that as no error; do not turn its first reasoning
# delta into a retry before the UI can receive it. Still inspect base_resp
# below, since a placeholder must not hide a real vendor failure.
if isinstance(top_level_error, Mapping) and top_level_error:
code = top_level_error.get("code")
if code is None:
code = top_level_error.get("type")
if code is None:
code = top_level_error.get("status_code")
message = top_level_error.get("message")
return code if code is not None else "unknown", message if isinstance(message, str) else None
if isinstance(top_level_error, str) and top_level_error:
return "unknown", top_level_error
raw = payload.get("base_resp") or payload.get("baseResp")
if not isinstance(raw, Mapping):
return None
code = raw.get("status_code")
if code is None:
code = raw.get("statusCode")
if code is None:
code = raw.get("code")
# MiniMax-compatible responses use 0 for success. Be conservative for
# gateways that put informational metadata in ``base_resp``.
normalized_code = str(code).strip().lower() if code is not None else ""
if normalized_code in {"", "0", "200", "ok", "success"}:
return None
message = raw.get("status_msg")
if message is None:
message = raw.get("statusMsg")
if message is None:
message = raw.get("message")
return code, message if isinstance(message, str) else None
def _raise_for_application_error(payload: Mapping[str, Any]) -> None:
error = _application_error(payload)
if error is not None:
code, message = error
raise OpenAICompatibleUpstreamError(code=code, message=message)
def _stream_choices(chunk: Mapping[str, Any]) -> list[dict[str, Any]]:
choices = chunk.get("choices")
if not isinstance(choices, list) or not choices:
nested = chunk.get("chunk")
if isinstance(nested, Mapping):
choices = nested.get("choices")
if not isinstance(choices, list):
return []
return [choice for choice in choices if isinstance(choice, dict)]
def _normalize_compatible_stream_shapes(
chunk: dict[str, Any],
diagnostics: _ToolCallStreamDiagnostics | None,
) -> None:
"""Normalize bounded OpenAI-compatible stream deviations in-place.
A few gateways return a full ``message`` in a streamed choice, put
``tool_calls`` directly on the choice, or encode ``function.arguments``
as an object instead of a JSON string. All three shapes carry the same
information as the OpenAI delta schema and can be repaired without
inspecting or logging their content.
"""
choices = _stream_choices(chunk)
if diagnostics is not None and diagnostics.strict_stream_integrity:
diagnostics.text_state.observe(choices)
for position, choice in enumerate(choices):
delta = choice.get("delta")
original_delta = dict(delta) if isinstance(delta, dict) else None
if not isinstance(delta, dict):
message = choice.get("message")
if isinstance(message, Mapping):
delta = dict(message)
choice["delta"] = delta
if diagnostics is not None:
diagnostics.message_as_delta_chunks += 1
elif isinstance(choice.get("tool_calls"), (list, Mapping)):
delta = {"tool_calls": choice["tool_calls"]}
choice["delta"] = delta
if diagnostics is not None:
diagnostics.root_tool_call_chunks += 1
elif delta is not None and diagnostics is not None:
diagnostics.mark_invalid()
continue
elif choice.get("finish_reason") and diagnostics is not None and diagnostics.strict_stream_integrity:
# Compatible endpoints sometimes send a null/absent delta on
# the terminal frame. Keep that frame's finish/usage metadata.
delta = {}
choice["delta"] = delta
else:
continue
message = choice.get("message")
if diagnostics is not None and diagnostics.strict_stream_integrity:
# Explicit message snapshots can contain the missing answer tail,
# or repeat text already delivered through standard deltas. Keep
# the accumulator on this invocation, never on the shared model.
diagnostics.text_state.normalize_text(
index=position,
delta=delta,
original_delta=original_delta,
message=message if isinstance(message, Mapping) else None,
)
elif isinstance(message, Mapping):
# Preserve existing reasoning compatibility for other models.
if _extract_reasoning_content(delta) is None:
for field_name in ("reasoning_content", "reasoning"):
value = message.get(field_name)
if isinstance(value, str) and value:
delta[field_name] = value
# Some gateways wrap a useful full-message/root tool call beside an
# otherwise empty ``delta``. Fill only a missing tool_calls field; a
# standard delta always wins and is never overwritten.
if delta.get("tool_calls") is None:
message = choice.get("message")
message_tool_calls = message.get("tool_calls") if isinstance(message, Mapping) else None
root_tool_calls = choice.get("tool_calls")
if isinstance(message_tool_calls, (list, Mapping)):
delta["tool_calls"] = message_tool_calls
if diagnostics is not None:
diagnostics.message_as_delta_chunks += 1
elif isinstance(root_tool_calls, (list, Mapping)):
delta["tool_calls"] = root_tool_calls
if diagnostics is not None:
diagnostics.root_tool_call_chunks += 1
tool_calls = delta.get("tool_calls")
if tool_calls is None:
continue
if isinstance(tool_calls, Mapping):
tool_calls = [dict(tool_calls)]
delta["tool_calls"] = tool_calls
if diagnostics is not None:
diagnostics.singleton_tool_call_chunks += 1
elif not isinstance(tool_calls, list):
if diagnostics is not None:
diagnostics.mark_invalid()
continue
for tool_call in tool_calls:
if not isinstance(tool_call, dict):
if diagnostics is not None:
diagnostics.mark_invalid()
continue
function = tool_call.get("function")
if function is None:
continue
if not isinstance(function, dict):
if diagnostics is not None:
diagnostics.mark_invalid()
continue
arguments = function.get("arguments")
if isinstance(arguments, (Mapping, list)):
function["arguments"] = json.dumps(
arguments,
ensure_ascii=False,
separators=(",", ":"),
)
if diagnostics is not None:
diagnostics.structured_arg_chunks += 1
elif arguments is not None and not isinstance(arguments, str):
if diagnostics is not None:
diagnostics.mark_invalid()
def _normalize_compatible_nonstream_shapes(response: dict[str, Any]) -> bool:
"""Normalize structured tool arguments before LangChain parses messages."""
changed = False
choices = response.get("choices")
if not isinstance(choices, list):
return changed
for choice in choices:
if not isinstance(choice, dict):
continue
message = choice.get("message")
if not isinstance(message, dict):
continue
tool_calls = message.get("tool_calls")
if isinstance(tool_calls, Mapping):
tool_calls = [dict(tool_calls)]
message["tool_calls"] = tool_calls
changed = True
if not isinstance(tool_calls, list):
continue
for tool_call in tool_calls:
if not isinstance(tool_call, dict):
continue
function = tool_call.get("function")
if not isinstance(function, dict):
continue
arguments = function.get("arguments")
if isinstance(arguments, (Mapping, list)):
function["arguments"] = json.dumps(
arguments,
ensure_ascii=False,
separators=(",", ":"),
)
changed = True
return changed
def rewrite_cumulative_tool_call_arguments(
chunk: dict[str, Any],
snapshots: dict[int, str],
*,
call_ids: dict[int, str] | None = None,
argument_modes: dict[int, str] | None = None,
tool_name_snapshots: dict[int, str] | None = None,
complete_tool_names: set[str] | None = None,
diagnostics: _ToolCallStreamDiagnostics | None = None,
) -> dict[int, str]:
"""Rewrite snapshot-style tool-call arguments into true deltas in-place.
Growing snapshots (each chunk repeats the JSON seen so far) become the
newly appended suffix. True OpenAI deltas are left unchanged. Duplicate
snapshots emit an empty string so LangChain does not concatenate twice.
"""
for choice in _stream_choices(chunk):
delta = choice.get("delta")
if not isinstance(delta, dict):
continue
tool_calls = delta.get("tool_calls")
if not isinstance(tool_calls, list):
continue
for position, tool_call in enumerate(tool_calls):
if not isinstance(tool_call, dict):
if diagnostics is not None:
diagnostics.mark_invalid()
continue
raw_call_id = tool_call.get("id")
raw_index = tool_call.get("index")
invalid_index = raw_index is None
if invalid_index:
index = position
else:
try:
index = int(raw_index)
except (TypeError, ValueError):
index = position
invalid_index = True
if invalid_index:
if call_ids is not None and isinstance(raw_call_id, str) and raw_call_id:
bounded_call_id = raw_call_id[:256]
for known_index, known_id in call_ids.items():
if known_id == bounded_call_id:
index = known_index
break
else:
# Parallel compatible gateways may emit one id-bearing
# tool call per chunk while omitting every index. Each
# item then has list position zero; allocate a free
# canonical slot instead of resetting the first call.
if index in call_ids and call_ids[index] != bounded_call_id:
index = 0
while index in call_ids:
index += 1
if diagnostics is not None:
diagnostics.mark_invalid()
# LangChain's OpenAI delta converter indexes every tool call with
# ``rtc["index"]``. Compatible gateways sometimes omit it (or send
# it as a numeric string); normalizing in-place keeps the call from
# being dropped after our snapshot repair.
tool_call["index"] = index
if diagnostics is not None:
diagnostics.seen_indices.add(index)
if call_ids is not None and isinstance(raw_call_id, str) and raw_call_id:
bounded_call_id = raw_call_id[:256]
previous_call_id = call_ids.get(index)
if previous_call_id is not None and previous_call_id != bounded_call_id:
# A new explicit call id reusing an index belongs to a new
# tool call. Keeping the old snapshot would concatenate two
# independent JSON documents and can prevent tool execution.
snapshots.pop(index, None)
if argument_modes is not None:
argument_modes.pop(index, None)
if tool_name_snapshots is not None:
tool_name_snapshots.pop(index, None)
if diagnostics is not None:
diagnostics.last_raw_lengths.pop(index, None)
diagnostics.tool_id_resets += 1
call_ids[index] = bounded_call_id
function = tool_call.get("function")
if not isinstance(function, dict):
if function is not None and diagnostics is not None:
diagnostics.mark_invalid()
continue
name = function.get("name")
if tool_name_snapshots is not None and isinstance(name, str) and name:
previous_name = tool_name_snapshots.get(index, "")
if previous_name == name and (snapshots.get(index) or name in (complete_tool_names or ())):
# Some gateways repeat the complete function name beside
# every argument snapshot/delta. LangChain concatenates
# names too, producing e.g. write_filewrite_file, which is
# no longer executable or recognizable by the live preview.
# Suppress only after arguments have started or a request
# tool schema proves this is the complete name. Legitimate
# name fragments (write_ + file, or foo + foo) stay intact.
function["name"] = None
if diagnostics is not None:
diagnostics.repeated_tool_names += 1
else:
tool_name_snapshots[index] = f"{previous_name}{name}"
arguments = function.get("arguments")
if not isinstance(arguments, str):
if arguments is not None and diagnostics is not None:
diagnostics.mark_invalid()
continue
if arguments == "":
if diagnostics is not None:
diagnostics.empty_arg_chunks += 1
continue
previous = snapshots.get(index, "")
mode = argument_modes.get(index, "unknown") if argument_modes is not None else "unknown"
if mode == "delta":
snapshots[index] = f"{previous}{arguments}" if previous else arguments
if diagnostics is not None:
diagnostics.observe_argument(
index=index,
raw_length=len(arguments),
classification="delta" if previous else "initial",
)
continue
if previous and arguments == previous:
function["arguments"] = ""
if argument_modes is not None:
argument_modes[index] = "cumulative"
if diagnostics is not None:
diagnostics.observe_argument(
index=index,
raw_length=len(arguments),
classification="repeated",
)
continue
if previous and arguments.startswith(previous):
function["arguments"] = arguments[len(previous) :]
snapshots[index] = arguments
if argument_modes is not None:
argument_modes[index] = "cumulative"
if diagnostics is not None:
diagnostics.observe_argument(
index=index,
raw_length=len(arguments),
classification="cumulative",
)
continue
# A shorter fragment that happens to be a prefix of the assembled
# JSON is ambiguous and is valid in ordinary delta streams (for
# example a nested ``{`` split). Never discard it as a rewind.
# Once a non-snapshot fragment appears, lock this call to delta
# mode so later chunks are always appended verbatim.
if previous and argument_modes is not None:
argument_modes[index] = "delta"
snapshots[index] = f"{previous}{arguments}" if previous else arguments
if diagnostics is not None:
diagnostics.observe_argument(
index=index,
raw_length=len(arguments),
classification="delta" if previous else "initial",
)
return snapshots
class PatchedChatOpenAI(ChatOpenAI):
"""ChatOpenAI with ``thought_signature`` preservation for Gemini thinking via OpenAI gateway.
When using Gemini with thinking enabled via an OpenAI-compatible gateway,
the API expects ``thought_signature`` to be present on tool-call objects in
multi-turn conversations. This patched version restores those signatures
from ``AIMessage.additional_kwargs["tool_calls"]`` into the serialised
request payload before it is sent to the API.
Usage in ``config.yaml``::
- name: gemini-2.5-pro-thinking
display_name: Gemini 2.5 Pro (Thinking)
use: deerflow.models.patched_openai:PatchedChatOpenAI
model: google/gemini-2.5-pro-preview
api_key: $GEMINI_API_KEY
base_url: https://<your-openai-compat-gateway>/v1
max_tokens: 16384
supports_thinking: true
supports_vision: true
when_thinking_enabled:
extra_body:
thinking:
type: enabled
"""
# DeerFlow-only transport capability. ``native`` is deliberately the
# default so every existing/unconfigured model stays on the exact original
# OpenAI tool-call path.
write_file_transport: WriteFileTransport = "native"
# Opt-in fallback for OpenAI-compatible DeepSeek V4/V4.1 endpoints whose
# serving layer leaves native DSML in assistant content. The factory keeps
# this local field out of the provider request.
tool_call_transport: Literal["native", "deepseek_dsml"] = "native"
# Local compatibility switch, never forwarded to the provider. None uses
# the affected model id; the factory also recognizes the configured alias.
strict_stream_integrity: bool | None = None
def _generate(self, *args: Any, **kwargs: Any) -> ChatResult:
"""Own non-stream log cleanup in the context that opened the request."""
capture = open_exchange()
token = bind_exchange(capture)
dsml_token = _ACTIVE_DSML_RECOVERY.set(
DSMLToolCallRecovery(enabled=self.tool_call_transport == "deepseek_dsml")
)
try:
return super()._generate(*args, **kwargs)
finally:
try:
finish_exchange(capture)
finally:
try:
_ACTIVE_DSML_RECOVERY.reset(dsml_token)
finally:
unbind_exchange(token)
async def _agenerate(self, *args: Any, **kwargs: Any) -> ChatResult:
# LangChain runs _create_chat_result in a copied executor context. A
# ContextVar token created by _get_request_payload cannot be reset
# there: even a correct model response would become a ValueError.
# Bind here and clean up after awaiting the complete provider call,
# including errors/cancellation, just as the streaming path does.
capture = open_exchange()
token = bind_exchange(capture)
dsml_token = _ACTIVE_DSML_RECOVERY.set(
DSMLToolCallRecovery(enabled=self.tool_call_transport == "deepseek_dsml")
)
try:
return await super()._agenerate(*args, **kwargs)
finally:
try:
finish_exchange(capture)
finally:
try:
_ACTIVE_DSML_RECOVERY.reset(dsml_token)
finally:
unbind_exchange(token)
def _stream(self, *args: Any, **kwargs: Any) -> Iterator[ChatGenerationChunk]:
"""Wrap one synchronous provider stream in isolated diagnostics state."""
# Test runners and application bootstrap code may replace warning
# filters after this module is imported. Reassert the same exact global
# rule at invocation start; unlike catch_warnings this holds no mutable
# context across iteration or network waits.
_install_structured_arg_warning_filter()
capture = open_exchange()
diagnostics = self._new_tool_stream_diagnostics(kwargs.get("run_manager"))
bridge_state = _WriteFileBridgeStreamState(transport=self.write_file_transport)
dsml_state = DSMLToolCallRecovery(enabled=self.tool_call_transport == "deepseek_dsml")
error: BaseException | None = None
parent_stream = super()._stream(*args, **kwargs)
iterator = iter(parent_stream)
tail_chunks: list[ChatGenerationChunk] = []
try:
while True:
# Scope both context vars to one provider ``next`` call. They are
# reset *before* yielding, so closing a suspended generator from
# another context cannot reset a foreign token.
exchange_token = bind_exchange(capture)
token = _ACTIVE_TOOL_STREAM.set(diagnostics)
bridge_token = _ACTIVE_WRITE_FILE_BRIDGE.set(bridge_state)
dsml_token = _ACTIVE_DSML_RECOVERY.set(dsml_state)
try:
generation_chunk = next(iterator)
except StopIteration:
tail_chunks = dsml_state.finish()
_ensure_write_file_bridge_finished(bridge_state)
diagnostics.text_state.ensure_finished()
break
finally:
_ACTIVE_DSML_RECOVERY.reset(dsml_token)
_ACTIVE_WRITE_FILE_BRIDGE.reset(bridge_token)
_ACTIVE_TOOL_STREAM.reset(token)
unbind_exchange(exchange_token)
yield from dsml_state.feed(generation_chunk)
if bridge_state.selection_alias_early_stop:
if (
bridge_state.transport == "buffered_gateway"
and bridge_state.pending is not None
):
close = getattr(iterator, "close", None)
if close is not None:
close()
bridge_state.selection_alias_early_stop = False
bridge_state.buffered_gateway_body_request = True
# Start a second provider request with the same original
# LangChain input. The active bridge state rewrites it
# into an isolated ordinary-text body request. Both
# responses remain one logical model stream to the
# caller, so ToolNode sees only the final write_file.
parent_stream = super()._stream(*args, **kwargs)
iterator = iter(parent_stream)
continue
_ensure_write_file_bridge_finished(bridge_state)
break
yield from tail_chunks
except BaseException as exc:
error = exc
raise
finally:
close_error: BaseException | None = None
close = getattr(iterator, "close", None)
if close is not None:
try:
close()
except BaseException as exc:
close_error = exc
_log_stream_cleanup_failure(exc, asynchronous=False)
diagnostics.log_summary(error or close_error)
finish_exchange(capture)
if error is None and close_error is not None:
raise close_error
async def _astream(self, *args: Any, **kwargs: Any) -> AsyncIterator[ChatGenerationChunk]:
"""Wrap one async provider stream in isolated diagnostics state.
A ``ContextVar`` keeps cumulative-argument snapshots invocation-local.
The former model-instance dictionary could be shared by overlapping
calls and concatenate one request's tool JSON onto another request.
"""
_install_structured_arg_warning_filter()
capture = open_exchange()
diagnostics = self._new_tool_stream_diagnostics(kwargs.get("run_manager"))
bridge_state = _WriteFileBridgeStreamState(transport=self.write_file_transport)
dsml_state = DSMLToolCallRecovery(enabled=self.tool_call_transport == "deepseek_dsml")
error: BaseException | None = None
parent_stream = super()._astream(*args, **kwargs)
iterator = parent_stream.__aiter__()
tail_chunks: list[ChatGenerationChunk] = []
try:
while True:
# Keep both context vars only around ``__anext__``. The converter
# runs inside this await, and each token is reset in that same
# context before control reaches yield.
exchange_token = bind_exchange(capture)
token = _ACTIVE_TOOL_STREAM.set(diagnostics)
bridge_token = _ACTIVE_WRITE_FILE_BRIDGE.set(bridge_state)
dsml_token = _ACTIVE_DSML_RECOVERY.set(dsml_state)
try:
generation_chunk = await iterator.__anext__()
except StopAsyncIteration:
tail_chunks = dsml_state.finish()
_ensure_write_file_bridge_finished(bridge_state)
diagnostics.text_state.ensure_finished()
break
finally:
_ACTIVE_DSML_RECOVERY.reset(dsml_token)
_ACTIVE_WRITE_FILE_BRIDGE.reset(bridge_token)
_ACTIVE_TOOL_STREAM.reset(token)
unbind_exchange(exchange_token)
for recovered_chunk in dsml_state.feed(generation_chunk):
yield recovered_chunk
if bridge_state.selection_alias_early_stop:
if (
bridge_state.transport == "buffered_gateway"
and bridge_state.pending is not None
):
aclose = getattr(iterator, "aclose", None)
if aclose is not None:
await aclose()
bridge_state.selection_alias_early_stop = False
bridge_state.buffered_gateway_body_request = True
parent_stream = super()._astream(*args, **kwargs)
iterator = parent_stream.__aiter__()
continue
_ensure_write_file_bridge_finished(bridge_state)
break
for recovered_chunk in tail_chunks:
yield recovered_chunk
except BaseException as exc:
error = exc
raise
finally:
close_error: BaseException | None = None
aclose = getattr(iterator, "aclose", None)
if aclose is not None:
try:
await aclose()
except BaseException as exc:
close_error = exc
_log_stream_cleanup_failure(exc, asynchronous=True)
diagnostics.log_summary(error or close_error)
finish_exchange(capture)
if error is None and close_error is not None:
raise close_error
def _new_tool_stream_diagnostics(self, run_manager: Any = None) -> _ToolCallStreamDiagnostics:
model_name = _safe_metric_name(
getattr(self, "model_name", None) or getattr(self, "model", None),
fallback="unknown",
)
run_id = _safe_metric_name(getattr(run_manager, "run_id", None), fallback="none")
strict = self.strict_stream_integrity
if strict is None:
strict = needs_qwen_flash_next_integrity(self.model_name)
return _ToolCallStreamDiagnostics(model_name=model_name, run_id=run_id, strict_stream_integrity=strict)
def _get_request_payload(
self,
input_: LanguageModelInput,
*,
stop: list[str] | None = None,
**kwargs: Any,
) -> dict:
"""Get request payload with ``thought_signature`` preserved on tool-call objects.
Overrides the parent method to re-inject ``thought_signature`` fields
on tool-call objects that were stored in
``additional_kwargs["tool_calls"]`` by LangChain but dropped during
serialisation.
"""
# Capture the original LangChain messages *before* conversion so we can
# access fields that the serialiser might drop.
original_messages = self._convert_input(input_).to_messages()
# Obtain the base payload from the parent implementation.
payload = super()._get_request_payload(input_, stop=stop, **kwargs)
payload_messages = payload.get("messages", [])
# Restore provider metadata while payload and source history still
# have their original one-to-one shape. The compatibility layer may
# subsequently remove older internal begin_write_file exchanges;
# doing this afterward could pair a later assistant turn with a
# removed control message.
if len(payload_messages) == len(original_messages):
for payload_msg, orig_msg in zip(payload_messages, original_messages):
if payload_msg.get("role") == "assistant" and isinstance(orig_msg, AIMessage):
_restore_tool_call_signatures(payload_msg, orig_msg)
_restore_reasoning_content(payload_msg, orig_msg)
else:
# Fallback: match assistant-role entries positionally against AIMessages.
ai_messages = [m for m in original_messages if isinstance(m, AIMessage)]
assistant_payloads = [(i, m) for i, m in enumerate(payload_messages) if m.get("role") == "assistant"]
for (_, payload_msg), ai_msg in zip(assistant_payloads, ai_messages):
_restore_tool_call_signatures(payload_msg, ai_msg)
_restore_reasoning_content(payload_msg, ai_msg)
bridge_state = _ACTIVE_WRITE_FILE_BRIDGE.get()
if bridge_state is not None:
_configure_write_file_bridge_payload(payload, original_messages, bridge_state)
else:
# invoke/ainvoke does not pass through our streaming ContextVar.
# Degrade a configured bridge to the original atomic write_file
# behavior, and defensively hide the internal tool for native
# models too if a caller bound it accidentally.
_hide_begin_write_file_for_native_request(payload)
diagnostics = _ACTIVE_TOOL_STREAM.get()
if diagnostics is not None:
extra_body = payload.get("extra_body")
value = extra_body.get("enable_thinking", payload.get("enable_thinking")) if isinstance(extra_body, Mapping) else payload.get("enable_thinking")
# Only an allowlisted boolean label; never stringify provider or
# user request values, which can contain prompt text or secrets.
diagnostics.request_enable_thinking = "true" if value is True else "false" if value is False else "unset" if value is None else "other"
tools = payload.get("tools")
if isinstance(tools, list):
for tool in tools:
function = tool.get("function") if isinstance(tool, Mapping) else None
name = function.get("name") if isinstance(function, Mapping) else None
if isinstance(name, str) and name:
diagnostics.complete_tool_names.add(name)
dsml_recovery = _ACTIVE_DSML_RECOVERY.get()
if dsml_recovery is not None:
dsml_recovery.configure_from_payload(payload)
normalize_chat_payload_messages(payload)
from deerflow.models.prompt_suffix import append_payload_prompt_suffix
append_payload_prompt_suffix(
payload,
getattr(self, "deerflow_prompt_model_name", ""),
enabled=getattr(self, "deerflow_prompt_suffix_enabled", True),
)
remember_request(payload)
return payload
def _convert_chunk_to_generation_chunk(
self,
chunk: dict,
default_chunk_class: type,
base_generation_info: dict | None,
) -> ChatGenerationChunk | None:
"""Preserve streamed ``reasoning_content``/``reasoning`` metadata."""
remember_provider_chunk(chunk)
diagnostics = _ACTIVE_TOOL_STREAM.get()
if diagnostics is not None:
diagnostics.observe_chunk(chunk)
# Some compatible gateways return a vendor error event followed by a
# normal-looking usage chunk, all under HTTP 200. Detect the first
# event before LangChain converts it into an empty AIMessageChunk.
_raise_for_application_error(chunk)
_normalize_compatible_stream_shapes(chunk, diagnostics)
bridge_state = _ACTIVE_WRITE_FILE_BRIDGE.get()
if bridge_state is not None:
_bridge_streamed_file_body(chunk, bridge_state)
if diagnostics is not None:
snapshots = diagnostics.snapshots
call_ids = diagnostics.call_ids
argument_modes = diagnostics.argument_modes
else:
snapshots = self._tool_arg_snapshots_for_chunk(chunk)
call_ids = None
argument_modes = None
rewrite_cumulative_tool_call_arguments(
chunk,
snapshots,
call_ids=call_ids,
argument_modes=argument_modes,
tool_name_snapshots=diagnostics.tool_name_snapshots if diagnostics is not None else None,
complete_tool_names=diagnostics.complete_tool_names if diagnostics is not None else None,
diagnostics=diagnostics,
)
if bridge_state is not None:
_rewrite_selection_write_file_alias(chunk, bridge_state)
choices = chunk.get("choices", []) or chunk.get("chunk", {}).get("choices", [])
choice = choices[0] if choices else {}
delta = choice.get("delta") if isinstance(choice, dict) else None
signature_records = _extract_tool_call_signature_records(delta.get("tool_calls") if isinstance(delta, dict) else None)
generation_chunk = super()._convert_chunk_to_generation_chunk(
chunk,
default_chunk_class,
base_generation_info,
)
if generation_chunk is None:
return None
if signature_records and isinstance(generation_chunk.message, AIMessageChunk):
generation_chunk.message = _append_tool_call_signature_metadata(
generation_chunk.message,
signature_records,
)
reasoning_content = _extract_reasoning_content(delta if isinstance(delta, dict) else None)
if reasoning_content and isinstance(generation_chunk.message, AIMessageChunk):
generation_chunk.message = _append_reasoning_content(
generation_chunk.message,
reasoning_content,
reasoning=delta.get("reasoning") if isinstance(delta, dict) else None,
)
if isinstance(choice, dict) and choice.get("finish_reason"):
if diagnostics is None:
self._cumulative_tool_arg_snapshots = {}
if diagnostics is not None and _extract_reasoning_content(generation_chunk.message.additional_kwargs):
diagnostics.emitted_reasoning_chunks += 1
return generation_chunk
def _tool_arg_snapshots_for_chunk(self, chunk: dict) -> dict[int, str]:
"""Return per-index argument snapshots, reset when a new completion starts."""
stream_id = chunk.get("id")
if isinstance(stream_id, str) and stream_id:
if stream_id != getattr(self, "_tool_arg_stream_id", None):
self._tool_arg_stream_id = stream_id
self._cumulative_tool_arg_snapshots = {}
snapshots = getattr(self, "_cumulative_tool_arg_snapshots", None)
if snapshots is None:
snapshots = {}
self._cumulative_tool_arg_snapshots = snapshots
return snapshots
def _create_chat_result(
self,
response: dict | Any,
generation_info: dict | None = None,
) -> ChatResult:
"""Preserve non-stream ``reasoning_content``/``reasoning`` metadata."""
response_dict = response if isinstance(response, dict) else response.model_dump(
warnings=False,
exclude={"choices": {"__all__": {"message": {"parsed"}}}},
)
remember_raw_response(response_dict)
_raise_for_application_error(response_dict)
_normalize_compatible_nonstream_shapes(response_dict)
dsml_recovery = _ACTIVE_DSML_RECOVERY.get()
if dsml_recovery is not None:
dsml_recovery.recover_nonstream_response(response_dict)
# Reuse the safe dump. Passing the typed response back to LangChain
# serializes it again with warnings enabled and can print provider data.
result = super()._create_chat_result(response_dict, generation_info)
choices = response_dict.get("choices", [])
generations: list[ChatGeneration] = []
for index, generation in enumerate(result.generations):
message = generation.message
choice = choices[index] if index < len(choices) else {}
choice_message = choice.get("message", {}) if isinstance(choice, dict) else {}
signature_records = _extract_tool_call_signature_records(choice_message.get("tool_calls") if isinstance(choice_message, dict) else None)
if signature_records and isinstance(message, AIMessage):
message = _append_tool_call_signature_metadata(message, signature_records)
reasoning_content = _extract_reasoning_content(choice_message if isinstance(choice_message, dict) else None)
if reasoning_content and isinstance(message, AIMessage):
message = _append_reasoning_content(
message,
reasoning_content,
reasoning=choice_message.get("reasoning") if isinstance(choice_message, dict) else None,
)
if message is not generation.message:
generation = ChatGeneration(message=message, generation_info=generation.generation_info)
generations.append(generation)
# Mirror LangChain's typed structured-output/refusal handling without
# serializing arbitrary parsed Pydantic objects into the raw response.
typed_choices = getattr(response, "choices", None)
if generations and isinstance(typed_choices, list) and typed_choices:
typed_message = typed_choices[0].message
for field_name in ("parsed", "refusal"):
if hasattr(typed_message, field_name):
generations[0].message.additional_kwargs[field_name] = getattr(typed_message, field_name)
if choices and isinstance(choices[0], dict):
message = choices[0].get("message")
if isinstance(message, dict):
remember_final_message(message)
finish_owned_exchange()
return ChatResult(generations=generations, llm_output=result.llm_output)
def _restore_tool_call_signatures(payload_msg: dict, orig_msg: AIMessage) -> None:
"""Re-inject ``thought_signature`` onto tool-call objects in *payload_msg*.
The private metadata is populated before LangChain discards provider
extension fields. Legacy messages that still retain raw ``tool_calls`` in
``additional_kwargs`` remain supported. Matching prefers call id and then
the canonical tool index, so concurrent tool calls cannot exchange tokens.
"""
payload_tool_calls: list[dict] = payload_msg.get("tool_calls") or []
if not payload_tool_calls:
return
raw_records = orig_msg.additional_kwargs.get(_TOOL_SIGNATURES_KEY)
records = [dict(record) for record in raw_records if isinstance(record, Mapping)] if isinstance(raw_records, list) else []
legacy_tool_calls = orig_msg.additional_kwargs.get("tool_calls")
records.extend(_extract_tool_call_signature_records(legacy_tool_calls))
if not records:
return
by_id: dict[str, dict[str, Any]] = {}
by_index: dict[int, dict[str, Any]] = {}
for record in records:
call_id = record.get("id")
if isinstance(call_id, str) and call_id:
by_id[call_id] = record
raw_index = record.get("tool_index", record.get("index"))
try:
record_index = int(raw_index)
except (TypeError, ValueError):
continue
by_index[record_index] = record
for idx, payload_tc in enumerate(payload_tool_calls):
call_id = payload_tc.get("id")
record = by_id.get(call_id) if isinstance(call_id, str) else None
if record is None:
record = by_index.get(idx)
if record is not None:
_restore_signature_at_location(payload_tc, record)
def _restore_reasoning_content(payload_msg: dict, orig_msg: AIMessage) -> None:
"""Re-inject provider reasoning onto an assistant history message.
Some thinking-enabled OpenAI-compatible providers require each historical
assistant turn to include its original reasoning in subsequent requests.
Preserve the spelling returned by the provider when available; otherwise
retain the established ``reasoning_content`` behavior.
"""
reasoning = orig_msg.additional_kwargs.get("reasoning")
if reasoning is not None:
payload_msg["reasoning"] = reasoning
return
reasoning_content = orig_msg.additional_kwargs.get("reasoning_content")
if reasoning_content is not None:
payload_msg["reasoning_content"] = reasoning_content
def _extract_reasoning_content(payload: dict | None) -> str | None:
"""Extract either supported reasoning spelling from provider payloads."""
if not isinstance(payload, dict):
return None
reasoning_content = payload.get("reasoning_content") or payload.get("reasoning")
return reasoning_content if isinstance(reasoning_content, str) and reasoning_content else None
def _append_reasoning_content(
message: AIMessage | AIMessageChunk,
reasoning_content: str,
*,
reasoning: Any = None,
) -> AIMessage | AIMessageChunk:
"""Normalize reasoning for clients while retaining its provider spelling."""
additional_kwargs = dict(message.additional_kwargs)
existing = additional_kwargs.get("reasoning_content")
if isinstance(existing, str) and existing:
if reasoning_content not in existing:
additional_kwargs["reasoning_content"] = f"{existing}{reasoning_content}"
else:
additional_kwargs["reasoning_content"] = reasoning_content
if reasoning is not None:
additional_kwargs["reasoning"] = reasoning
return message.model_copy(update={"additional_kwargs": additional_kwargs})