package anthropicprovider import ( "context" "encoding/json" "fmt" "github.com/anthropics/anthropic-sdk-go" "github.com/anthropics/anthropic-sdk-go/option" ) type ToolCall struct { ID string `json:"id"` Type string `json:"type,omitempty"` Function *FunctionCall `json:"function,omitempty"` Name string `json:"name,omitempty"` Arguments map[string]interface{} `json:"arguments,omitempty"` } type FunctionCall struct { Name string `json:"name"` Arguments string `json:"arguments"` } type LLMResponse struct { Content string `json:"content"` ToolCalls []ToolCall `json:"tool_calls,omitempty"` FinishReason string `json:"finish_reason"` Usage *UsageInfo `json:"usage,omitempty"` } type UsageInfo struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` TotalTokens int `json:"total_tokens"` } type Message struct { Role string `json:"role"` Content string `json:"content"` ToolCalls []ToolCall `json:"tool_calls,omitempty"` ToolCallID string `json:"tool_call_id,omitempty"` } type ToolDefinition struct { Type string `json:"type"` Function ToolFunctionDefinition `json:"function"` } type ToolFunctionDefinition struct { Name string `json:"name"` Description string `json:"description"` Parameters map[string]interface{} `json:"parameters"` } type Provider struct { client *anthropic.Client tokenSource func() (string, error) } func NewProvider(token string) *Provider { client := anthropic.NewClient( option.WithAuthToken(token), option.WithBaseURL("https://api.anthropic.com"), ) return &Provider{client: &client} } func NewProviderWithClient(client *anthropic.Client) *Provider { return &Provider{client: client} } func NewProviderWithTokenSource(token string, tokenSource func() (string, error)) *Provider { p := NewProvider(token) p.tokenSource = tokenSource return p } func (p *Provider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { var opts []option.RequestOption if p.tokenSource != nil { tok, err := p.tokenSource() if err != nil { return nil, fmt.Errorf("refreshing token: %w", err) } opts = append(opts, option.WithAuthToken(tok)) } params, err := buildParams(messages, tools, model, options) if err != nil { return nil, err } resp, err := p.client.Messages.New(ctx, params, opts...) if err != nil { return nil, fmt.Errorf("claude API call: %w", err) } return parseResponse(resp), nil } func (p *Provider) GetDefaultModel() string { return "claude-sonnet-4-5-20250929" } func buildParams(messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (anthropic.MessageNewParams, error) { var system []anthropic.TextBlockParam var anthropicMessages []anthropic.MessageParam for _, msg := range messages { switch msg.Role { case "system": system = append(system, anthropic.TextBlockParam{Text: msg.Content}) case "user": if msg.ToolCallID != "" { anthropicMessages = append(anthropicMessages, anthropic.NewUserMessage(anthropic.NewToolResultBlock(msg.ToolCallID, msg.Content, false)), ) } else { anthropicMessages = append(anthropicMessages, anthropic.NewUserMessage(anthropic.NewTextBlock(msg.Content)), ) } case "assistant": if len(msg.ToolCalls) > 0 { var blocks []anthropic.ContentBlockParamUnion if msg.Content != "" { blocks = append(blocks, anthropic.NewTextBlock(msg.Content)) } for _, tc := range msg.ToolCalls { blocks = append(blocks, anthropic.NewToolUseBlock(tc.ID, tc.Arguments, tc.Name)) } anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(blocks...)) } else { anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(anthropic.NewTextBlock(msg.Content)), ) } case "tool": anthropicMessages = append(anthropicMessages, anthropic.NewUserMessage(anthropic.NewToolResultBlock(msg.ToolCallID, msg.Content, false)), ) } } maxTokens := int64(4096) if mt, ok := options["max_tokens"].(int); ok { maxTokens = int64(mt) } params := anthropic.MessageNewParams{ Model: anthropic.Model(model), Messages: anthropicMessages, MaxTokens: maxTokens, } if len(system) > 0 { params.System = system } if temp, ok := options["temperature"].(float64); ok { params.Temperature = anthropic.Float(temp) } if len(tools) > 0 { params.Tools = translateTools(tools) } return params, nil } func translateTools(tools []ToolDefinition) []anthropic.ToolUnionParam { result := make([]anthropic.ToolUnionParam, 0, len(tools)) for _, t := range tools { tool := anthropic.ToolParam{ Name: t.Function.Name, InputSchema: anthropic.ToolInputSchemaParam{ Properties: t.Function.Parameters["properties"], }, } if desc := t.Function.Description; desc != "" { tool.Description = anthropic.String(desc) } if req, ok := t.Function.Parameters["required"].([]interface{}); ok { required := make([]string, 0, len(req)) for _, r := range req { if s, ok := r.(string); ok { required = append(required, s) } } tool.InputSchema.Required = required } result = append(result, anthropic.ToolUnionParam{OfTool: &tool}) } return result } func parseResponse(resp *anthropic.Message) *LLMResponse { var content string var toolCalls []ToolCall for _, block := range resp.Content { switch block.Type { case "text": tb := block.AsText() content += tb.Text case "tool_use": tu := block.AsToolUse() var args map[string]interface{} if err := json.Unmarshal(tu.Input, &args); err != nil { args = map[string]interface{}{"raw": string(tu.Input)} } toolCalls = append(toolCalls, ToolCall{ ID: tu.ID, Name: tu.Name, Arguments: args, }) } } finishReason := "stop" switch resp.StopReason { case anthropic.StopReasonToolUse: finishReason = "tool_calls" case anthropic.StopReasonMaxTokens: finishReason = "length" case anthropic.StopReasonEndTurn: finishReason = "stop" } return &LLMResponse{ Content: content, ToolCalls: toolCalls, FinishReason: finishReason, Usage: &UsageInfo{ PromptTokens: int(resp.Usage.InputTokens), CompletionTokens: int(resp.Usage.OutputTokens), TotalTokens: int(resp.Usage.InputTokens + resp.Usage.OutputTokens), }, } }