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[