diff --git a/ccproxy/llms/formatters/anthropic_to_openai/responses.py b/ccproxy/llms/formatters/anthropic_to_openai/responses.py
index a4aa957d..ec808131 100644
--- a/ccproxy/llms/formatters/anthropic_to_openai/responses.py
+++ b/ccproxy/llms/formatters/anthropic_to_openai/responses.py
@@ -44,13 +44,10 @@ def convert__anthropic_message_to_openai_responses__response(
text_parts.append(getattr(block, "text", ""))
elif block_type == "thinking":
thinking = getattr(block, "thinking", None) or ""
- signature = getattr(block, "signature", None)
- sig_attr = (
- f' signature="{signature}"'
- if isinstance(signature, str) and signature
- else ""
- )
- text_parts.append(f"{thinking}")
+ text_parts.append(f"{thinking}")
+ elif block_type == "redacted_thinking":
+ # Skip redacted thinking blocks
+ continue
elif block_type == "tool_use":
tool_contents.append(
{
@@ -113,14 +110,11 @@ def convert__anthropic_message_to_openai_chat__response(
parts.append(text)
elif btype == "thinking":
thinking = getattr(block, "thinking", None)
- signature = getattr(block, "signature", None)
if isinstance(thinking, str):
- sig_attr = (
- f' signature="{signature}"'
- if isinstance(signature, str) and signature
- else ""
- )
- parts.append(f"{thinking}")
+ parts.append(f"{thinking}")
+ # Skip redacted_thinking blocks
+ elif btype == "redacted_thinking":
+ continue
elif btype == "tool_use":
tool_calls.append(
build_openai_tool_call(
diff --git a/ccproxy/llms/formatters/anthropic_to_openai/streams.py b/ccproxy/llms/formatters/anthropic_to_openai/streams.py
index 3aff75f0..afb6cc56 100644
--- a/ccproxy/llms/formatters/anthropic_to_openai/streams.py
+++ b/ccproxy/llms/formatters/anthropic_to_openai/streams.py
@@ -73,13 +73,11 @@ def _anthropic_delta_to_text(
block_type = block_meta.get("type")
if block_type == "thinking":
+ # Return just the thinking text - tags handled by block start/stop events
thinking_text = delta.get("thinking")
- if not isinstance(thinking_text, str) or not thinking_text:
- return None
- signature = block_meta.get("signature")
- if isinstance(signature, str) and signature:
- return f'{thinking_text}'
- return f"{thinking_text}"
+ if isinstance(thinking_text, str) and thinking_text:
+ return thinking_text
+ return None
text_val = delta.get("text")
if isinstance(text_val, str) and text_val:
@@ -1378,6 +1376,34 @@ async def generator() -> AsyncGenerator[
if not message_started:
continue
+ if event_type == "content_block_start":
+ content_block = (
+ event_payload.get("content_block", {})
+ if isinstance(event_payload, dict)
+ else {}
+ )
+ if (
+ isinstance(content_block, dict)
+ and content_block.get("type") == "thinking"
+ ):
+ # Emit opening tag
+ yield openai_models.ChatCompletionChunk(
+ id="chatcmpl-stream",
+ object="chat.completion.chunk",
+ created=0,
+ model=model_id,
+ choices=[
+ openai_models.StreamingChoice(
+ index=0,
+ delta=openai_models.DeltaMessage(
+ role="assistant", content=""
+ ),
+ finish_reason=None,
+ )
+ ],
+ )
+ continue
+
if event_type == "content_block_delta":
block_index = int(event_payload.get("index", 0))
text_delta = _anthropic_delta_to_text(
@@ -1409,7 +1435,28 @@ async def generator() -> AsyncGenerator[
if not block_info:
continue
_, block_meta = block_info
- if block_meta.get("type") != "tool_use":
+ block_type = block_meta.get("type")
+
+ if block_type == "thinking":
+ # Emit closing tag
+ yield openai_models.ChatCompletionChunk(
+ id="chatcmpl-stream",
+ object="chat.completion.chunk",
+ created=0,
+ model=model_id,
+ choices=[
+ openai_models.StreamingChoice(
+ index=0,
+ delta=openai_models.DeltaMessage(
+ role="assistant", content=""
+ ),
+ finish_reason=None,
+ )
+ ],
+ )
+ continue
+
+ if block_type != "tool_use":
continue
if block_index in emitted_tool_indices:
continue
diff --git a/ccproxy/llms/models/anthropic.py b/ccproxy/llms/models/anthropic.py
index a5b7186e..fd934fe1 100644
--- a/ccproxy/llms/models/anthropic.py
+++ b/ccproxy/llms/models/anthropic.py
@@ -219,7 +219,13 @@ class RedactedThinkingBlock(ContentBlockBase):
RequestContentBlock = Annotated[
- TextBlock | ImageBlock | ToolUseBlock | ToolResultBlock, Field(discriminator="type")
+ TextBlock
+ | ImageBlock
+ | ToolUseBlock
+ | ToolResultBlock
+ | ThinkingBlock
+ | RedactedThinkingBlock,
+ Field(discriminator="type"),
]
ResponseContentBlock = Annotated[