diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index cc14ceaf0..ec2cfe4e8 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -378,8 +378,9 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M // Build assistant message with tool calls assistantMsg := providers.Message{ - Role: "assistant", - Content: response.Content, + Role: "assistant", + Content: response.Content, + ThinkingContent: response.ThinkingContent, } for _, tc := range response.ToolCalls { argumentsJSON, _ := json.Marshal(tc.Arguments) diff --git a/pkg/providers/claude_provider.go b/pkg/providers/claude_provider.go index ae6aca96d..8f89ee5ea 100644 --- a/pkg/providers/claude_provider.go +++ b/pkg/providers/claude_provider.go @@ -77,6 +77,14 @@ func buildClaudeParams(messages []Message, tools []ToolDefinition, model string, case "assistant": if len(msg.ToolCalls) > 0 { var blocks []anthropic.ContentBlockParamUnion + // Add thinking block if present (required for extended thinking mode) + if msg.ThinkingContent != "" { + blocks = append(blocks, anthropic.ContentBlockParamUnion{ + OfThinking: &anthropic.ThinkingBlockParam{ + Thinking: msg.ThinkingContent, + }, + }) + } if msg.Content != "" { blocks = append(blocks, anthropic.NewTextBlock(msg.Content)) } @@ -85,9 +93,16 @@ func buildClaudeParams(messages []Message, tools []ToolDefinition, model string, } anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(blocks...)) } else { - anthropicMessages = append(anthropicMessages, - anthropic.NewAssistantMessage(anthropic.NewTextBlock(msg.Content)), - ) + var blocks []anthropic.ContentBlockParamUnion + if msg.ThinkingContent != "" { + blocks = append(blocks, anthropic.ContentBlockParamUnion{ + OfThinking: &anthropic.ThinkingBlockParam{ + Thinking: msg.ThinkingContent, + }, + }) + } + blocks = append(blocks, anthropic.NewTextBlock(msg.Content)) + anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(blocks...)) } case "tool": anthropicMessages = append(anthropicMessages, @@ -150,10 +165,14 @@ func translateToolsForClaude(tools []ToolDefinition) []anthropic.ToolUnionParam func parseClaudeResponse(resp *anthropic.Message) *LLMResponse { var content string + var thinkingContent string var toolCalls []ToolCall for _, block := range resp.Content { switch block.Type { + case "thinking": + tb := block.AsThinking() + thinkingContent += tb.Thinking case "text": tb := block.AsText() content += tb.Text @@ -182,9 +201,10 @@ func parseClaudeResponse(resp *anthropic.Message) *LLMResponse { } return &LLMResponse{ - Content: content, - ToolCalls: toolCalls, - FinishReason: finishReason, + Content: content, + ThinkingContent: thinkingContent, + ToolCalls: toolCalls, + FinishReason: finishReason, Usage: &UsageInfo{ PromptTokens: int(resp.Usage.InputTokens), CompletionTokens: int(resp.Usage.OutputTokens), diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index 6f188e2f0..1b0b44199 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -60,9 +60,41 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too } } + // Convert messages to API format, including reasoning_content for thinking models + apiMessages := make([]map[string]interface{}, 0, len(messages)) + for _, msg := range messages { + apiMsg := map[string]interface{}{ + "role": msg.Role, + "content": msg.Content, + } + // Include reasoning_content for assistant messages with thinking content + if msg.Role == "assistant" && msg.ThinkingContent != "" { + apiMsg["reasoning_content"] = msg.ThinkingContent + } + if len(msg.ToolCalls) > 0 { + toolCalls := make([]map[string]interface{}, 0, len(msg.ToolCalls)) + for _, tc := range msg.ToolCalls { + argsJSON, _ := json.Marshal(tc.Arguments) + toolCalls = append(toolCalls, map[string]interface{}{ + "id": tc.ID, + "type": "function", + "function": map[string]interface{}{ + "name": tc.Name, + "arguments": string(argsJSON), + }, + }) + } + apiMsg["tool_calls"] = toolCalls + } + if msg.ToolCallID != "" { + apiMsg["tool_call_id"] = msg.ToolCallID + } + apiMessages = append(apiMessages, apiMsg) + } + requestBody := map[string]interface{}{ "model": model, - "messages": messages, + "messages": apiMessages, } if len(tools) > 0 { @@ -126,8 +158,9 @@ func (p *HTTPProvider) parseResponse(body []byte) (*LLMResponse, error) { var apiResponse struct { Choices []struct { Message struct { - Content string `json:"content"` - ToolCalls []struct { + Content string `json:"content"` + ReasoningContent string `json:"reasoning_content"` + ToolCalls []struct { ID string `json:"id"` Type string `json:"type"` Function *struct { @@ -185,10 +218,11 @@ func (p *HTTPProvider) parseResponse(body []byte) (*LLMResponse, error) { } return &LLMResponse{ - Content: choice.Message.Content, - ToolCalls: toolCalls, - FinishReason: choice.FinishReason, - Usage: apiResponse.Usage, + Content: choice.Message.Content, + ThinkingContent: choice.Message.ReasoningContent, + ToolCalls: toolCalls, + FinishReason: choice.FinishReason, + Usage: apiResponse.Usage, }, nil } diff --git a/pkg/providers/types.go b/pkg/providers/types.go index 88b62e975..5c7fa2ba9 100644 --- a/pkg/providers/types.go +++ b/pkg/providers/types.go @@ -16,10 +16,11 @@ type FunctionCall struct { } type LLMResponse struct { - Content string `json:"content"` - ToolCalls []ToolCall `json:"tool_calls,omitempty"` - FinishReason string `json:"finish_reason"` - Usage *UsageInfo `json:"usage,omitempty"` + Content string `json:"content"` + ToolCalls []ToolCall `json:"tool_calls,omitempty"` + FinishReason string `json:"finish_reason"` + Usage *UsageInfo `json:"usage,omitempty"` + ThinkingContent string `json:"thinking_content,omitempty"` // For Claude extended thinking } type UsageInfo struct { @@ -29,10 +30,11 @@ type UsageInfo struct { } type Message struct { - Role string `json:"role"` - Content string `json:"content"` - ToolCalls []ToolCall `json:"tool_calls,omitempty"` - ToolCallID string `json:"tool_call_id,omitempty"` + Role string `json:"role"` + Content string `json:"content"` + ToolCalls []ToolCall `json:"tool_calls,omitempty"` + ToolCallID string `json:"tool_call_id,omitempty"` + ThinkingContent string `json:"thinking_content,omitempty"` // For Claude extended thinking } type LLMProvider interface {