anthropic.go

  1// Package anthropic provides an implementation of the fantasy AI SDK for Anthropic's language models.
  2package anthropic
  3
  4import (
  5	"cmp"
  6	"context"
  7	"encoding/base64"
  8	"encoding/json"
  9	"errors"
 10	"fmt"
 11	"io"
 12	"maps"
 13	"strings"
 14
 15	"charm.land/fantasy"
 16	"github.com/charmbracelet/anthropic-sdk-go"
 17	"github.com/charmbracelet/anthropic-sdk-go/bedrock"
 18	"github.com/charmbracelet/anthropic-sdk-go/option"
 19	"github.com/charmbracelet/anthropic-sdk-go/packages/param"
 20	"github.com/charmbracelet/anthropic-sdk-go/vertex"
 21	"golang.org/x/oauth2/google"
 22)
 23
 24const (
 25	// Name is the name of the Anthropic provider.
 26	Name = "anthropic"
 27	// DefaultURL is the default URL for the Anthropic API.
 28	DefaultURL = "https://api.anthropic.com"
 29)
 30
 31type options struct {
 32	baseURL string
 33	apiKey  string
 34	name    string
 35	headers map[string]string
 36	client  option.HTTPClient
 37
 38	vertexProject  string
 39	vertexLocation string
 40	skipAuth       bool
 41
 42	useBedrock bool
 43}
 44
 45type provider struct {
 46	options options
 47}
 48
 49// Option defines a function that configures Anthropic provider options.
 50type Option = func(*options)
 51
 52// New creates a new Anthropic provider with the given options.
 53func New(opts ...Option) (fantasy.Provider, error) {
 54	providerOptions := options{
 55		headers: map[string]string{},
 56	}
 57	for _, o := range opts {
 58		o(&providerOptions)
 59	}
 60
 61	providerOptions.baseURL = cmp.Or(providerOptions.baseURL, DefaultURL)
 62	providerOptions.name = cmp.Or(providerOptions.name, Name)
 63	return &provider{options: providerOptions}, nil
 64}
 65
 66// WithBaseURL sets the base URL for the Anthropic provider.
 67func WithBaseURL(baseURL string) Option {
 68	return func(o *options) {
 69		o.baseURL = baseURL
 70	}
 71}
 72
 73// WithAPIKey sets the API key for the Anthropic provider.
 74func WithAPIKey(apiKey string) Option {
 75	return func(o *options) {
 76		o.apiKey = apiKey
 77	}
 78}
 79
 80// WithVertex configures the Anthropic provider to use Vertex AI.
 81func WithVertex(project, location string) Option {
 82	return func(o *options) {
 83		o.vertexProject = project
 84		o.vertexLocation = location
 85	}
 86}
 87
 88// WithSkipAuth configures whether to skip authentication for the Anthropic provider.
 89func WithSkipAuth(skip bool) Option {
 90	return func(o *options) {
 91		o.skipAuth = skip
 92	}
 93}
 94
 95// WithBedrock configures the Anthropic provider to use AWS Bedrock.
 96func WithBedrock() Option {
 97	return func(o *options) {
 98		o.useBedrock = true
 99	}
100}
101
102// WithName sets the name for the Anthropic provider.
103func WithName(name string) Option {
104	return func(o *options) {
105		o.name = name
106	}
107}
108
109// WithHeaders sets the headers for the Anthropic provider.
110func WithHeaders(headers map[string]string) Option {
111	return func(o *options) {
112		maps.Copy(o.headers, headers)
113	}
114}
115
116// WithHTTPClient sets the HTTP client for the Anthropic provider.
117func WithHTTPClient(client option.HTTPClient) Option {
118	return func(o *options) {
119		o.client = client
120	}
121}
122
123func (a *provider) LanguageModel(ctx context.Context, modelID string) (fantasy.LanguageModel, error) {
124	clientOptions := make([]option.RequestOption, 0, 5+len(a.options.headers))
125	if a.options.apiKey != "" && !a.options.useBedrock {
126		clientOptions = append(clientOptions, option.WithAPIKey(a.options.apiKey))
127	}
128	if a.options.baseURL != "" {
129		clientOptions = append(clientOptions, option.WithBaseURL(a.options.baseURL))
130	}
131	for key, value := range a.options.headers {
132		clientOptions = append(clientOptions, option.WithHeader(key, value))
133	}
134	if a.options.client != nil {
135		clientOptions = append(clientOptions, option.WithHTTPClient(a.options.client))
136	}
137	if a.options.vertexProject != "" && a.options.vertexLocation != "" {
138		var credentials *google.Credentials
139		if a.options.skipAuth {
140			credentials = &google.Credentials{TokenSource: &googleDummyTokenSource{}}
141		} else {
142			var err error
143			credentials, err = google.FindDefaultCredentials(ctx)
144			if err != nil {
145				return nil, err
146			}
147		}
148
149		clientOptions = append(
150			clientOptions,
151			vertex.WithCredentials(
152				ctx,
153				a.options.vertexLocation,
154				a.options.vertexProject,
155				credentials,
156			),
157		)
158	}
159	if a.options.useBedrock {
160		if a.options.skipAuth || a.options.apiKey != "" {
161			clientOptions = append(
162				clientOptions,
163				bedrock.WithConfig(bedrockBasicAuthConfig(a.options.apiKey)),
164			)
165		} else {
166			clientOptions = append(
167				clientOptions,
168				bedrock.WithLoadDefaultConfig(ctx),
169			)
170		}
171	}
172	return languageModel{
173		modelID:  modelID,
174		provider: a.options.name,
175		options:  a.options,
176		client:   anthropic.NewClient(clientOptions...),
177	}, nil
178}
179
180type languageModel struct {
181	provider string
182	modelID  string
183	client   anthropic.Client
184	options  options
185}
186
187// Model implements fantasy.LanguageModel.
188func (a languageModel) Model() string {
189	return a.modelID
190}
191
192// Provider implements fantasy.LanguageModel.
193func (a languageModel) Provider() string {
194	return a.provider
195}
196
197func (a languageModel) prepareParams(call fantasy.Call) (*anthropic.MessageNewParams, []fantasy.CallWarning, error) {
198	params := &anthropic.MessageNewParams{}
199	providerOptions := &ProviderOptions{}
200	if v, ok := call.ProviderOptions[Name]; ok {
201		providerOptions, ok = v.(*ProviderOptions)
202		if !ok {
203			return nil, nil, fantasy.NewInvalidArgumentError("providerOptions", "anthropic provider options should be *anthropic.ProviderOptions", nil)
204		}
205	}
206	sendReasoning := true
207	if providerOptions.SendReasoning != nil {
208		sendReasoning = *providerOptions.SendReasoning
209	}
210	systemBlocks, messages, warnings := toPrompt(call.Prompt, sendReasoning)
211
212	if call.FrequencyPenalty != nil {
213		warnings = append(warnings, fantasy.CallWarning{
214			Type:    fantasy.CallWarningTypeUnsupportedSetting,
215			Setting: "FrequencyPenalty",
216		})
217	}
218	if call.PresencePenalty != nil {
219		warnings = append(warnings, fantasy.CallWarning{
220			Type:    fantasy.CallWarningTypeUnsupportedSetting,
221			Setting: "PresencePenalty",
222		})
223	}
224
225	params.System = systemBlocks
226	params.Messages = messages
227	params.Model = anthropic.Model(a.modelID)
228	params.MaxTokens = 4096
229
230	if call.MaxOutputTokens != nil {
231		params.MaxTokens = *call.MaxOutputTokens
232	}
233
234	if call.Temperature != nil {
235		params.Temperature = param.NewOpt(*call.Temperature)
236	}
237	if call.TopK != nil {
238		params.TopK = param.NewOpt(*call.TopK)
239	}
240	if call.TopP != nil {
241		params.TopP = param.NewOpt(*call.TopP)
242	}
243
244	isThinking := false
245	var thinkingBudget int64
246	if providerOptions.Thinking != nil {
247		isThinking = true
248		thinkingBudget = providerOptions.Thinking.BudgetTokens
249	}
250	if isThinking {
251		if thinkingBudget == 0 {
252			return nil, nil, fantasy.NewUnsupportedFunctionalityError("thinking requires budget", "")
253		}
254		params.Thinking = anthropic.ThinkingConfigParamOfEnabled(thinkingBudget)
255		if call.Temperature != nil {
256			params.Temperature = param.Opt[float64]{}
257			warnings = append(warnings, fantasy.CallWarning{
258				Type:    fantasy.CallWarningTypeUnsupportedSetting,
259				Setting: "temperature",
260				Details: "temperature is not supported when thinking is enabled",
261			})
262		}
263		if call.TopP != nil {
264			params.TopP = param.Opt[float64]{}
265			warnings = append(warnings, fantasy.CallWarning{
266				Type:    fantasy.CallWarningTypeUnsupportedSetting,
267				Setting: "TopP",
268				Details: "TopP is not supported when thinking is enabled",
269			})
270		}
271		if call.TopK != nil {
272			params.TopK = param.Opt[int64]{}
273			warnings = append(warnings, fantasy.CallWarning{
274				Type:    fantasy.CallWarningTypeUnsupportedSetting,
275				Setting: "TopK",
276				Details: "TopK is not supported when thinking is enabled",
277			})
278		}
279		params.MaxTokens = params.MaxTokens + thinkingBudget
280	}
281
282	if len(call.Tools) > 0 {
283		disableParallelToolUse := false
284		if providerOptions.DisableParallelToolUse != nil {
285			disableParallelToolUse = *providerOptions.DisableParallelToolUse
286		}
287		tools, toolChoice, toolWarnings := a.toTools(call.Tools, call.ToolChoice, disableParallelToolUse)
288		params.Tools = tools
289		if toolChoice != nil {
290			params.ToolChoice = *toolChoice
291		}
292		warnings = append(warnings, toolWarnings...)
293	}
294
295	return params, warnings, nil
296}
297
298func (a *provider) Name() string {
299	return Name
300}
301
302// GetCacheControl extracts cache control settings from provider options.
303func GetCacheControl(providerOptions fantasy.ProviderOptions) *CacheControl {
304	if anthropicOptions, ok := providerOptions[Name]; ok {
305		if options, ok := anthropicOptions.(*ProviderCacheControlOptions); ok {
306			return &options.CacheControl
307		}
308	}
309	return nil
310}
311
312// GetReasoningMetadata extracts reasoning metadata from provider options.
313func GetReasoningMetadata(providerOptions fantasy.ProviderOptions) *ReasoningOptionMetadata {
314	if anthropicOptions, ok := providerOptions[Name]; ok {
315		if reasoning, ok := anthropicOptions.(*ReasoningOptionMetadata); ok {
316			return reasoning
317		}
318	}
319	return nil
320}
321
322type messageBlock struct {
323	Role     fantasy.MessageRole
324	Messages []fantasy.Message
325}
326
327func groupIntoBlocks(prompt fantasy.Prompt) []*messageBlock {
328	var blocks []*messageBlock
329
330	var currentBlock *messageBlock
331
332	for _, msg := range prompt {
333		switch msg.Role {
334		case fantasy.MessageRoleSystem:
335			if currentBlock == nil || currentBlock.Role != fantasy.MessageRoleSystem {
336				currentBlock = &messageBlock{
337					Role:     fantasy.MessageRoleSystem,
338					Messages: []fantasy.Message{},
339				}
340				blocks = append(blocks, currentBlock)
341			}
342			currentBlock.Messages = append(currentBlock.Messages, msg)
343		case fantasy.MessageRoleUser:
344			if currentBlock == nil || currentBlock.Role != fantasy.MessageRoleUser {
345				currentBlock = &messageBlock{
346					Role:     fantasy.MessageRoleUser,
347					Messages: []fantasy.Message{},
348				}
349				blocks = append(blocks, currentBlock)
350			}
351			currentBlock.Messages = append(currentBlock.Messages, msg)
352		case fantasy.MessageRoleAssistant:
353			if currentBlock == nil || currentBlock.Role != fantasy.MessageRoleAssistant {
354				currentBlock = &messageBlock{
355					Role:     fantasy.MessageRoleAssistant,
356					Messages: []fantasy.Message{},
357				}
358				blocks = append(blocks, currentBlock)
359			}
360			currentBlock.Messages = append(currentBlock.Messages, msg)
361		case fantasy.MessageRoleTool:
362			if currentBlock == nil || currentBlock.Role != fantasy.MessageRoleUser {
363				currentBlock = &messageBlock{
364					Role:     fantasy.MessageRoleUser,
365					Messages: []fantasy.Message{},
366				}
367				blocks = append(blocks, currentBlock)
368			}
369			currentBlock.Messages = append(currentBlock.Messages, msg)
370		}
371	}
372	return blocks
373}
374
375func (a languageModel) toTools(tools []fantasy.Tool, toolChoice *fantasy.ToolChoice, disableParallelToolCalls bool) (anthropicTools []anthropic.ToolUnionParam, anthropicToolChoice *anthropic.ToolChoiceUnionParam, warnings []fantasy.CallWarning) {
376	for _, tool := range tools {
377		if tool.GetType() == fantasy.ToolTypeFunction {
378			ft, ok := tool.(fantasy.FunctionTool)
379			if !ok {
380				continue
381			}
382			required := []string{}
383			var properties any
384			if props, ok := ft.InputSchema["properties"]; ok {
385				properties = props
386			}
387			if req, ok := ft.InputSchema["required"]; ok {
388				if reqArr, ok := req.([]string); ok {
389					required = reqArr
390				}
391			}
392			cacheControl := GetCacheControl(ft.ProviderOptions)
393
394			anthropicTool := anthropic.ToolParam{
395				Name:        ft.Name,
396				Description: anthropic.String(ft.Description),
397				InputSchema: anthropic.ToolInputSchemaParam{
398					Properties: properties,
399					Required:   required,
400				},
401			}
402			if cacheControl != nil {
403				anthropicTool.CacheControl = anthropic.NewCacheControlEphemeralParam()
404			}
405			anthropicTools = append(anthropicTools, anthropic.ToolUnionParam{OfTool: &anthropicTool})
406			continue
407		}
408		// TODO: handle provider tool calls
409		warnings = append(warnings, fantasy.CallWarning{
410			Type:    fantasy.CallWarningTypeUnsupportedTool,
411			Tool:    tool,
412			Message: "tool is not supported",
413		})
414	}
415
416	// NOTE: Bedrock does not support this attribute.
417	var disableParallelToolUse param.Opt[bool]
418	if !a.options.useBedrock {
419		disableParallelToolUse = param.NewOpt(disableParallelToolCalls)
420	}
421
422	if toolChoice == nil {
423		if disableParallelToolCalls {
424			anthropicToolChoice = &anthropic.ToolChoiceUnionParam{
425				OfAuto: &anthropic.ToolChoiceAutoParam{
426					Type:                   "auto",
427					DisableParallelToolUse: disableParallelToolUse,
428				},
429			}
430		}
431		return anthropicTools, anthropicToolChoice, warnings
432	}
433
434	switch *toolChoice {
435	case fantasy.ToolChoiceAuto:
436		anthropicToolChoice = &anthropic.ToolChoiceUnionParam{
437			OfAuto: &anthropic.ToolChoiceAutoParam{
438				Type:                   "auto",
439				DisableParallelToolUse: disableParallelToolUse,
440			},
441		}
442	case fantasy.ToolChoiceRequired:
443		anthropicToolChoice = &anthropic.ToolChoiceUnionParam{
444			OfAny: &anthropic.ToolChoiceAnyParam{
445				Type:                   "any",
446				DisableParallelToolUse: disableParallelToolUse,
447			},
448		}
449	case fantasy.ToolChoiceNone:
450		return anthropicTools, anthropicToolChoice, warnings
451	default:
452		anthropicToolChoice = &anthropic.ToolChoiceUnionParam{
453			OfTool: &anthropic.ToolChoiceToolParam{
454				Type:                   "tool",
455				Name:                   string(*toolChoice),
456				DisableParallelToolUse: disableParallelToolUse,
457			},
458		}
459	}
460	return anthropicTools, anthropicToolChoice, warnings
461}
462
463func toPrompt(prompt fantasy.Prompt, sendReasoningData bool) ([]anthropic.TextBlockParam, []anthropic.MessageParam, []fantasy.CallWarning) {
464	var systemBlocks []anthropic.TextBlockParam
465	var messages []anthropic.MessageParam
466	var warnings []fantasy.CallWarning
467
468	blocks := groupIntoBlocks(prompt)
469	finishedSystemBlock := false
470	for _, block := range blocks {
471		switch block.Role {
472		case fantasy.MessageRoleSystem:
473			if finishedSystemBlock {
474				// skip multiple system messages that are separated by user/assistant messages
475				// TODO: see if we need to send error here?
476				continue
477			}
478			finishedSystemBlock = true
479			for _, msg := range block.Messages {
480				for i, part := range msg.Content {
481					isLastPart := i == len(msg.Content)-1
482					cacheControl := GetCacheControl(part.Options())
483					if cacheControl == nil && isLastPart {
484						cacheControl = GetCacheControl(msg.ProviderOptions)
485					}
486					text, ok := fantasy.AsMessagePart[fantasy.TextPart](part)
487					if !ok {
488						continue
489					}
490					textBlock := anthropic.TextBlockParam{
491						Text: text.Text,
492					}
493					if cacheControl != nil {
494						textBlock.CacheControl = anthropic.NewCacheControlEphemeralParam()
495					}
496					systemBlocks = append(systemBlocks, textBlock)
497				}
498			}
499
500		case fantasy.MessageRoleUser:
501			var anthropicContent []anthropic.ContentBlockParamUnion
502			for _, msg := range block.Messages {
503				if msg.Role == fantasy.MessageRoleUser {
504					for i, part := range msg.Content {
505						isLastPart := i == len(msg.Content)-1
506						cacheControl := GetCacheControl(part.Options())
507						if cacheControl == nil && isLastPart {
508							cacheControl = GetCacheControl(msg.ProviderOptions)
509						}
510						switch part.GetType() {
511						case fantasy.ContentTypeText:
512							text, ok := fantasy.AsMessagePart[fantasy.TextPart](part)
513							if !ok {
514								continue
515							}
516							textBlock := &anthropic.TextBlockParam{
517								Text: text.Text,
518							}
519							if cacheControl != nil {
520								textBlock.CacheControl = anthropic.NewCacheControlEphemeralParam()
521							}
522							anthropicContent = append(anthropicContent, anthropic.ContentBlockParamUnion{
523								OfText: textBlock,
524							})
525						case fantasy.ContentTypeFile:
526							file, ok := fantasy.AsMessagePart[fantasy.FilePart](part)
527							if !ok {
528								continue
529							}
530							// TODO: handle other file types
531							if !strings.HasPrefix(file.MediaType, "image/") {
532								continue
533							}
534
535							base64Encoded := base64.StdEncoding.EncodeToString(file.Data)
536							imageBlock := anthropic.NewImageBlockBase64(file.MediaType, base64Encoded)
537							if cacheControl != nil {
538								imageBlock.OfImage.CacheControl = anthropic.NewCacheControlEphemeralParam()
539							}
540							anthropicContent = append(anthropicContent, imageBlock)
541						}
542					}
543				} else if msg.Role == fantasy.MessageRoleTool {
544					for i, part := range msg.Content {
545						isLastPart := i == len(msg.Content)-1
546						cacheControl := GetCacheControl(part.Options())
547						if cacheControl == nil && isLastPart {
548							cacheControl = GetCacheControl(msg.ProviderOptions)
549						}
550						result, ok := fantasy.AsMessagePart[fantasy.ToolResultPart](part)
551						if !ok {
552							continue
553						}
554						toolResultBlock := anthropic.ToolResultBlockParam{
555							ToolUseID: result.ToolCallID,
556						}
557						switch result.Output.GetType() {
558						case fantasy.ToolResultContentTypeText:
559							content, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentText](result.Output)
560							if !ok {
561								continue
562							}
563							toolResultBlock.Content = []anthropic.ToolResultBlockParamContentUnion{
564								{
565									OfText: &anthropic.TextBlockParam{
566										Text: content.Text,
567									},
568								},
569							}
570						case fantasy.ToolResultContentTypeMedia:
571							content, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentMedia](result.Output)
572							if !ok {
573								continue
574							}
575							toolResultBlock.Content = []anthropic.ToolResultBlockParamContentUnion{
576								{
577									OfImage: anthropic.NewImageBlockBase64(content.MediaType, content.Data).OfImage,
578								},
579							}
580						case fantasy.ToolResultContentTypeError:
581							content, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentError](result.Output)
582							if !ok {
583								continue
584							}
585							toolResultBlock.Content = []anthropic.ToolResultBlockParamContentUnion{
586								{
587									OfText: &anthropic.TextBlockParam{
588										Text: content.Error.Error(),
589									},
590								},
591							}
592							toolResultBlock.IsError = param.NewOpt(true)
593						}
594						if cacheControl != nil {
595							toolResultBlock.CacheControl = anthropic.NewCacheControlEphemeralParam()
596						}
597						anthropicContent = append(anthropicContent, anthropic.ContentBlockParamUnion{
598							OfToolResult: &toolResultBlock,
599						})
600					}
601				}
602			}
603			messages = append(messages, anthropic.NewUserMessage(anthropicContent...))
604		case fantasy.MessageRoleAssistant:
605			var anthropicContent []anthropic.ContentBlockParamUnion
606			for _, msg := range block.Messages {
607				for i, part := range msg.Content {
608					isLastPart := i == len(msg.Content)-1
609					cacheControl := GetCacheControl(part.Options())
610					if cacheControl == nil && isLastPart {
611						cacheControl = GetCacheControl(msg.ProviderOptions)
612					}
613					switch part.GetType() {
614					case fantasy.ContentTypeText:
615						text, ok := fantasy.AsMessagePart[fantasy.TextPart](part)
616						if !ok {
617							continue
618						}
619						textBlock := &anthropic.TextBlockParam{
620							Text: text.Text,
621						}
622						if cacheControl != nil {
623							textBlock.CacheControl = anthropic.NewCacheControlEphemeralParam()
624						}
625						anthropicContent = append(anthropicContent, anthropic.ContentBlockParamUnion{
626							OfText: textBlock,
627						})
628					case fantasy.ContentTypeReasoning:
629						reasoning, ok := fantasy.AsMessagePart[fantasy.ReasoningPart](part)
630						if !ok {
631							continue
632						}
633						if !sendReasoningData {
634							warnings = append(warnings, fantasy.CallWarning{
635								Type:    "other",
636								Message: "sending reasoning content is disabled for this model",
637							})
638							continue
639						}
640						reasoningMetadata := GetReasoningMetadata(part.Options())
641						if reasoningMetadata == nil {
642							warnings = append(warnings, fantasy.CallWarning{
643								Type:    "other",
644								Message: "unsupported reasoning metadata",
645							})
646							continue
647						}
648
649						if reasoningMetadata.Signature != "" {
650							anthropicContent = append(anthropicContent, anthropic.NewThinkingBlock(reasoningMetadata.Signature, reasoning.Text))
651						} else if reasoningMetadata.RedactedData != "" {
652							anthropicContent = append(anthropicContent, anthropic.NewRedactedThinkingBlock(reasoningMetadata.RedactedData))
653						} else {
654							warnings = append(warnings, fantasy.CallWarning{
655								Type:    "other",
656								Message: "unsupported reasoning metadata",
657							})
658							continue
659						}
660					case fantasy.ContentTypeToolCall:
661						toolCall, ok := fantasy.AsMessagePart[fantasy.ToolCallPart](part)
662						if !ok {
663							continue
664						}
665						if toolCall.ProviderExecuted {
666							// TODO: implement provider executed call
667							continue
668						}
669
670						var inputMap map[string]any
671						err := json.Unmarshal([]byte(toolCall.Input), &inputMap)
672						if err != nil {
673							continue
674						}
675						toolUseBlock := anthropic.NewToolUseBlock(toolCall.ToolCallID, inputMap, toolCall.ToolName)
676						if cacheControl != nil {
677							toolUseBlock.OfToolUse.CacheControl = anthropic.NewCacheControlEphemeralParam()
678						}
679						anthropicContent = append(anthropicContent, toolUseBlock)
680					case fantasy.ContentTypeToolResult:
681						// TODO: implement provider executed tool result
682					}
683				}
684			}
685			messages = append(messages, anthropic.NewAssistantMessage(anthropicContent...))
686		}
687	}
688	return systemBlocks, messages, warnings
689}
690
691func (a languageModel) handleError(err error) error {
692	var apiErr *anthropic.Error
693	if errors.As(err, &apiErr) {
694		requestDump := apiErr.DumpRequest(true)
695		responseDump := apiErr.DumpResponse(true)
696		headers := map[string]string{}
697		for k, h := range apiErr.Response.Header {
698			v := h[len(h)-1]
699			headers[strings.ToLower(k)] = v
700		}
701		return fantasy.NewAPICallError(
702			apiErr.Error(),
703			apiErr.Request.URL.String(),
704			string(requestDump),
705			apiErr.StatusCode,
706			headers,
707			string(responseDump),
708			apiErr,
709			false,
710		)
711	}
712	return err
713}
714
715func mapFinishReason(finishReason string) fantasy.FinishReason {
716	switch finishReason {
717	case "end_turn", "pause_turn", "stop_sequence":
718		return fantasy.FinishReasonStop
719	case "max_tokens":
720		return fantasy.FinishReasonLength
721	case "tool_use":
722		return fantasy.FinishReasonToolCalls
723	default:
724		return fantasy.FinishReasonUnknown
725	}
726}
727
728// Generate implements fantasy.LanguageModel.
729func (a languageModel) Generate(ctx context.Context, call fantasy.Call) (*fantasy.Response, error) {
730	params, warnings, err := a.prepareParams(call)
731	if err != nil {
732		return nil, err
733	}
734	response, err := a.client.Messages.New(ctx, *params)
735	if err != nil {
736		return nil, a.handleError(err)
737	}
738
739	var content []fantasy.Content
740	for _, block := range response.Content {
741		switch block.Type {
742		case "text":
743			text, ok := block.AsAny().(anthropic.TextBlock)
744			if !ok {
745				continue
746			}
747			content = append(content, fantasy.TextContent{
748				Text: text.Text,
749			})
750		case "thinking":
751			reasoning, ok := block.AsAny().(anthropic.ThinkingBlock)
752			if !ok {
753				continue
754			}
755			content = append(content, fantasy.ReasoningContent{
756				Text: reasoning.Thinking,
757				ProviderMetadata: fantasy.ProviderMetadata{
758					Name: &ReasoningOptionMetadata{
759						Signature: reasoning.Signature,
760					},
761				},
762			})
763		case "redacted_thinking":
764			reasoning, ok := block.AsAny().(anthropic.RedactedThinkingBlock)
765			if !ok {
766				continue
767			}
768			content = append(content, fantasy.ReasoningContent{
769				Text: "",
770				ProviderMetadata: fantasy.ProviderMetadata{
771					Name: &ReasoningOptionMetadata{
772						RedactedData: reasoning.Data,
773					},
774				},
775			})
776		case "tool_use":
777			toolUse, ok := block.AsAny().(anthropic.ToolUseBlock)
778			if !ok {
779				continue
780			}
781			content = append(content, fantasy.ToolCallContent{
782				ToolCallID:       toolUse.ID,
783				ToolName:         toolUse.Name,
784				Input:            string(toolUse.Input),
785				ProviderExecuted: false,
786			})
787		}
788	}
789
790	return &fantasy.Response{
791		Content: content,
792		Usage: fantasy.Usage{
793			InputTokens:         response.Usage.InputTokens,
794			OutputTokens:        response.Usage.OutputTokens,
795			TotalTokens:         response.Usage.InputTokens + response.Usage.OutputTokens,
796			CacheCreationTokens: response.Usage.CacheCreationInputTokens,
797			CacheReadTokens:     response.Usage.CacheReadInputTokens,
798		},
799		FinishReason:     mapFinishReason(string(response.StopReason)),
800		ProviderMetadata: fantasy.ProviderMetadata{},
801		Warnings:         warnings,
802	}, nil
803}
804
805// Stream implements fantasy.LanguageModel.
806func (a languageModel) Stream(ctx context.Context, call fantasy.Call) (fantasy.StreamResponse, error) {
807	params, warnings, err := a.prepareParams(call)
808	if err != nil {
809		return nil, err
810	}
811
812	stream := a.client.Messages.NewStreaming(ctx, *params)
813	acc := anthropic.Message{}
814	return func(yield func(fantasy.StreamPart) bool) {
815		if len(warnings) > 0 {
816			if !yield(fantasy.StreamPart{
817				Type:     fantasy.StreamPartTypeWarnings,
818				Warnings: warnings,
819			}) {
820				return
821			}
822		}
823
824		for stream.Next() {
825			chunk := stream.Current()
826			_ = acc.Accumulate(chunk)
827			switch chunk.Type {
828			case "content_block_start":
829				contentBlockType := chunk.ContentBlock.Type
830				switch contentBlockType {
831				case "text":
832					if !yield(fantasy.StreamPart{
833						Type: fantasy.StreamPartTypeTextStart,
834						ID:   fmt.Sprintf("%d", chunk.Index),
835					}) {
836						return
837					}
838				case "thinking":
839					if !yield(fantasy.StreamPart{
840						Type: fantasy.StreamPartTypeReasoningStart,
841						ID:   fmt.Sprintf("%d", chunk.Index),
842					}) {
843						return
844					}
845				case "redacted_thinking":
846					if !yield(fantasy.StreamPart{
847						Type: fantasy.StreamPartTypeReasoningStart,
848						ID:   fmt.Sprintf("%d", chunk.Index),
849						ProviderMetadata: fantasy.ProviderMetadata{
850							Name: &ReasoningOptionMetadata{
851								RedactedData: chunk.ContentBlock.Data,
852							},
853						},
854					}) {
855						return
856					}
857				case "tool_use":
858					if !yield(fantasy.StreamPart{
859						Type:          fantasy.StreamPartTypeToolInputStart,
860						ID:            chunk.ContentBlock.ID,
861						ToolCallName:  chunk.ContentBlock.Name,
862						ToolCallInput: "",
863					}) {
864						return
865					}
866				}
867			case "content_block_stop":
868				if len(acc.Content)-1 < int(chunk.Index) {
869					continue
870				}
871				contentBlock := acc.Content[int(chunk.Index)]
872				switch contentBlock.Type {
873				case "text":
874					if !yield(fantasy.StreamPart{
875						Type: fantasy.StreamPartTypeTextEnd,
876						ID:   fmt.Sprintf("%d", chunk.Index),
877					}) {
878						return
879					}
880				case "thinking":
881					if !yield(fantasy.StreamPart{
882						Type: fantasy.StreamPartTypeReasoningEnd,
883						ID:   fmt.Sprintf("%d", chunk.Index),
884					}) {
885						return
886					}
887				case "tool_use":
888					if !yield(fantasy.StreamPart{
889						Type: fantasy.StreamPartTypeToolInputEnd,
890						ID:   contentBlock.ID,
891					}) {
892						return
893					}
894					if !yield(fantasy.StreamPart{
895						Type:          fantasy.StreamPartTypeToolCall,
896						ID:            contentBlock.ID,
897						ToolCallName:  contentBlock.Name,
898						ToolCallInput: string(contentBlock.Input),
899					}) {
900						return
901					}
902				}
903			case "content_block_delta":
904				switch chunk.Delta.Type {
905				case "text_delta":
906					if !yield(fantasy.StreamPart{
907						Type:  fantasy.StreamPartTypeTextDelta,
908						ID:    fmt.Sprintf("%d", chunk.Index),
909						Delta: chunk.Delta.Text,
910					}) {
911						return
912					}
913				case "thinking_delta":
914					if !yield(fantasy.StreamPart{
915						Type:  fantasy.StreamPartTypeReasoningDelta,
916						ID:    fmt.Sprintf("%d", chunk.Index),
917						Delta: chunk.Delta.Thinking,
918					}) {
919						return
920					}
921				case "signature_delta":
922					if !yield(fantasy.StreamPart{
923						Type: fantasy.StreamPartTypeReasoningDelta,
924						ID:   fmt.Sprintf("%d", chunk.Index),
925						ProviderMetadata: fantasy.ProviderMetadata{
926							Name: &ReasoningOptionMetadata{
927								Signature: chunk.Delta.Signature,
928							},
929						},
930					}) {
931						return
932					}
933				case "input_json_delta":
934					if len(acc.Content)-1 < int(chunk.Index) {
935						continue
936					}
937					contentBlock := acc.Content[int(chunk.Index)]
938					if !yield(fantasy.StreamPart{
939						Type:          fantasy.StreamPartTypeToolInputDelta,
940						ID:            contentBlock.ID,
941						ToolCallInput: chunk.Delta.PartialJSON,
942					}) {
943						return
944					}
945				}
946			case "message_stop":
947			}
948		}
949
950		err := stream.Err()
951		if err == nil || errors.Is(err, io.EOF) {
952			yield(fantasy.StreamPart{
953				Type:         fantasy.StreamPartTypeFinish,
954				ID:           acc.ID,
955				FinishReason: mapFinishReason(string(acc.StopReason)),
956				Usage: fantasy.Usage{
957					InputTokens:         acc.Usage.InputTokens,
958					OutputTokens:        acc.Usage.OutputTokens,
959					TotalTokens:         acc.Usage.InputTokens + acc.Usage.OutputTokens,
960					CacheCreationTokens: acc.Usage.CacheCreationInputTokens,
961					CacheReadTokens:     acc.Usage.CacheReadInputTokens,
962				},
963				ProviderMetadata: fantasy.ProviderMetadata{},
964			})
965			return
966		} else { //nolint: revive
967			yield(fantasy.StreamPart{
968				Type:  fantasy.StreamPartTypeError,
969				Error: a.handleError(err),
970			})
971			return
972		}
973	}, nil
974}