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/anthropics/anthropic-sdk-go"
 17	"github.com/anthropics/anthropic-sdk-go/bedrock"
 18	"github.com/anthropics/anthropic-sdk-go/option"
 19	"github.com/anthropics/anthropic-sdk-go/packages/param"
 20	"github.com/anthropics/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 {
 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}
 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(modelID string) (fantasy.LanguageModel, error) {
124	clientOptions := make([]option.RequestOption, 0, 5+len(a.options.headers))
125	if a.options.apiKey != "" {
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(context.TODO())
144			if err != nil {
145				return nil, err
146			}
147		}
148
149		clientOptions = append(
150			clientOptions,
151			vertex.WithCredentials(
152				context.TODO(),
153				a.options.vertexLocation,
154				a.options.vertexProject,
155				credentials,
156			),
157		)
158	}
159	if a.options.useBedrock {
160		if a.options.skipAuth {
161			clientOptions = append(
162				clientOptions,
163				bedrock.WithConfig(dummyBedrockConfig),
164			)
165		} else {
166			clientOptions = append(
167				clientOptions,
168				bedrock.WithLoadDefaultConfig(context.TODO()),
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
302func getCacheControl(providerOptions fantasy.ProviderOptions) *CacheControl {
303	if anthropicOptions, ok := providerOptions[Name]; ok {
304		if options, ok := anthropicOptions.(*ProviderCacheControlOptions); ok {
305			return &options.CacheControl
306		}
307	}
308	return nil
309}
310
311func getReasoningMetadata(providerOptions fantasy.ProviderOptions) *ReasoningOptionMetadata {
312	if anthropicOptions, ok := providerOptions[Name]; ok {
313		if reasoning, ok := anthropicOptions.(*ReasoningOptionMetadata); ok {
314			return reasoning
315		}
316	}
317	return nil
318}
319
320type messageBlock struct {
321	Role     fantasy.MessageRole
322	Messages []fantasy.Message
323}
324
325func groupIntoBlocks(prompt fantasy.Prompt) []*messageBlock {
326	var blocks []*messageBlock
327
328	var currentBlock *messageBlock
329
330	for _, msg := range prompt {
331		switch msg.Role {
332		case fantasy.MessageRoleSystem:
333			if currentBlock == nil || currentBlock.Role != fantasy.MessageRoleSystem {
334				currentBlock = &messageBlock{
335					Role:     fantasy.MessageRoleSystem,
336					Messages: []fantasy.Message{},
337				}
338				blocks = append(blocks, currentBlock)
339			}
340			currentBlock.Messages = append(currentBlock.Messages, msg)
341		case fantasy.MessageRoleUser:
342			if currentBlock == nil || currentBlock.Role != fantasy.MessageRoleUser {
343				currentBlock = &messageBlock{
344					Role:     fantasy.MessageRoleUser,
345					Messages: []fantasy.Message{},
346				}
347				blocks = append(blocks, currentBlock)
348			}
349			currentBlock.Messages = append(currentBlock.Messages, msg)
350		case fantasy.MessageRoleAssistant:
351			if currentBlock == nil || currentBlock.Role != fantasy.MessageRoleAssistant {
352				currentBlock = &messageBlock{
353					Role:     fantasy.MessageRoleAssistant,
354					Messages: []fantasy.Message{},
355				}
356				blocks = append(blocks, currentBlock)
357			}
358			currentBlock.Messages = append(currentBlock.Messages, msg)
359		case fantasy.MessageRoleTool:
360			if currentBlock == nil || currentBlock.Role != fantasy.MessageRoleUser {
361				currentBlock = &messageBlock{
362					Role:     fantasy.MessageRoleUser,
363					Messages: []fantasy.Message{},
364				}
365				blocks = append(blocks, currentBlock)
366			}
367			currentBlock.Messages = append(currentBlock.Messages, msg)
368		}
369	}
370	return blocks
371}
372
373func (a languageModel) toTools(tools []fantasy.Tool, toolChoice *fantasy.ToolChoice, disableParallelToolCalls bool) (anthropicTools []anthropic.ToolUnionParam, anthropicToolChoice *anthropic.ToolChoiceUnionParam, warnings []fantasy.CallWarning) {
374	for _, tool := range tools {
375		if tool.GetType() == fantasy.ToolTypeFunction {
376			ft, ok := tool.(fantasy.FunctionTool)
377			if !ok {
378				continue
379			}
380			required := []string{}
381			var properties any
382			if props, ok := ft.InputSchema["properties"]; ok {
383				properties = props
384			}
385			if req, ok := ft.InputSchema["required"]; ok {
386				if reqArr, ok := req.([]string); ok {
387					required = reqArr
388				}
389			}
390			cacheControl := getCacheControl(ft.ProviderOptions)
391
392			anthropicTool := anthropic.ToolParam{
393				Name:        ft.Name,
394				Description: anthropic.String(ft.Description),
395				InputSchema: anthropic.ToolInputSchemaParam{
396					Properties: properties,
397					Required:   required,
398				},
399			}
400			if cacheControl != nil {
401				anthropicTool.CacheControl = anthropic.NewCacheControlEphemeralParam()
402			}
403			anthropicTools = append(anthropicTools, anthropic.ToolUnionParam{OfTool: &anthropicTool})
404			continue
405		}
406		// TODO: handle provider tool calls
407		warnings = append(warnings, fantasy.CallWarning{
408			Type:    fantasy.CallWarningTypeUnsupportedTool,
409			Tool:    tool,
410			Message: "tool is not supported",
411		})
412	}
413
414	// NOTE: Bedrock does not support this attribute.
415	var disableParallelToolUse param.Opt[bool]
416	if !a.options.useBedrock {
417		disableParallelToolUse = param.NewOpt(disableParallelToolCalls)
418	}
419
420	if toolChoice == nil {
421		if disableParallelToolCalls {
422			anthropicToolChoice = &anthropic.ToolChoiceUnionParam{
423				OfAuto: &anthropic.ToolChoiceAutoParam{
424					Type:                   "auto",
425					DisableParallelToolUse: disableParallelToolUse,
426				},
427			}
428		}
429		return anthropicTools, anthropicToolChoice, warnings
430	}
431
432	switch *toolChoice {
433	case fantasy.ToolChoiceAuto:
434		anthropicToolChoice = &anthropic.ToolChoiceUnionParam{
435			OfAuto: &anthropic.ToolChoiceAutoParam{
436				Type:                   "auto",
437				DisableParallelToolUse: disableParallelToolUse,
438			},
439		}
440	case fantasy.ToolChoiceRequired:
441		anthropicToolChoice = &anthropic.ToolChoiceUnionParam{
442			OfAny: &anthropic.ToolChoiceAnyParam{
443				Type:                   "any",
444				DisableParallelToolUse: disableParallelToolUse,
445			},
446		}
447	case fantasy.ToolChoiceNone:
448		return anthropicTools, anthropicToolChoice, warnings
449	default:
450		anthropicToolChoice = &anthropic.ToolChoiceUnionParam{
451			OfTool: &anthropic.ToolChoiceToolParam{
452				Type:                   "tool",
453				Name:                   string(*toolChoice),
454				DisableParallelToolUse: disableParallelToolUse,
455			},
456		}
457	}
458	return anthropicTools, anthropicToolChoice, warnings
459}
460
461func toPrompt(prompt fantasy.Prompt, sendReasoningData bool) ([]anthropic.TextBlockParam, []anthropic.MessageParam, []fantasy.CallWarning) {
462	var systemBlocks []anthropic.TextBlockParam
463	var messages []anthropic.MessageParam
464	var warnings []fantasy.CallWarning
465
466	blocks := groupIntoBlocks(prompt)
467	finishedSystemBlock := false
468	for _, block := range blocks {
469		switch block.Role {
470		case fantasy.MessageRoleSystem:
471			if finishedSystemBlock {
472				// skip multiple system messages that are separated by user/assistant messages
473				// TODO: see if we need to send error here?
474				continue
475			}
476			finishedSystemBlock = true
477			for _, msg := range block.Messages {
478				for i, part := range msg.Content {
479					isLastPart := i == len(msg.Content)-1
480					cacheControl := getCacheControl(part.Options())
481					if cacheControl == nil && isLastPart {
482						cacheControl = getCacheControl(msg.ProviderOptions)
483					}
484					text, ok := fantasy.AsMessagePart[fantasy.TextPart](part)
485					if !ok {
486						continue
487					}
488					textBlock := anthropic.TextBlockParam{
489						Text: text.Text,
490					}
491					if cacheControl != nil {
492						textBlock.CacheControl = anthropic.NewCacheControlEphemeralParam()
493					}
494					systemBlocks = append(systemBlocks, textBlock)
495				}
496			}
497
498		case fantasy.MessageRoleUser:
499			var anthropicContent []anthropic.ContentBlockParamUnion
500			for _, msg := range block.Messages {
501				if msg.Role == fantasy.MessageRoleUser {
502					for i, part := range msg.Content {
503						isLastPart := i == len(msg.Content)-1
504						cacheControl := getCacheControl(part.Options())
505						if cacheControl == nil && isLastPart {
506							cacheControl = getCacheControl(msg.ProviderOptions)
507						}
508						switch part.GetType() {
509						case fantasy.ContentTypeText:
510							text, ok := fantasy.AsMessagePart[fantasy.TextPart](part)
511							if !ok {
512								continue
513							}
514							textBlock := &anthropic.TextBlockParam{
515								Text: text.Text,
516							}
517							if cacheControl != nil {
518								textBlock.CacheControl = anthropic.NewCacheControlEphemeralParam()
519							}
520							anthropicContent = append(anthropicContent, anthropic.ContentBlockParamUnion{
521								OfText: textBlock,
522							})
523						case fantasy.ContentTypeFile:
524							file, ok := fantasy.AsMessagePart[fantasy.FilePart](part)
525							if !ok {
526								continue
527							}
528							// TODO: handle other file types
529							if !strings.HasPrefix(file.MediaType, "image/") {
530								continue
531							}
532
533							base64Encoded := base64.StdEncoding.EncodeToString(file.Data)
534							imageBlock := anthropic.NewImageBlockBase64(file.MediaType, base64Encoded)
535							if cacheControl != nil {
536								imageBlock.OfImage.CacheControl = anthropic.NewCacheControlEphemeralParam()
537							}
538							anthropicContent = append(anthropicContent, imageBlock)
539						}
540					}
541				} else if msg.Role == fantasy.MessageRoleTool {
542					for i, part := range msg.Content {
543						isLastPart := i == len(msg.Content)-1
544						cacheControl := getCacheControl(part.Options())
545						if cacheControl == nil && isLastPart {
546							cacheControl = getCacheControl(msg.ProviderOptions)
547						}
548						result, ok := fantasy.AsMessagePart[fantasy.ToolResultPart](part)
549						if !ok {
550							continue
551						}
552						toolResultBlock := anthropic.ToolResultBlockParam{
553							ToolUseID: result.ToolCallID,
554						}
555						switch result.Output.GetType() {
556						case fantasy.ToolResultContentTypeText:
557							content, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentText](result.Output)
558							if !ok {
559								continue
560							}
561							toolResultBlock.Content = []anthropic.ToolResultBlockParamContentUnion{
562								{
563									OfText: &anthropic.TextBlockParam{
564										Text: content.Text,
565									},
566								},
567							}
568						case fantasy.ToolResultContentTypeMedia:
569							content, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentMedia](result.Output)
570							if !ok {
571								continue
572							}
573							toolResultBlock.Content = []anthropic.ToolResultBlockParamContentUnion{
574								{
575									OfImage: anthropic.NewImageBlockBase64(content.MediaType, content.Data).OfImage,
576								},
577							}
578						case fantasy.ToolResultContentTypeError:
579							content, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentError](result.Output)
580							if !ok {
581								continue
582							}
583							toolResultBlock.Content = []anthropic.ToolResultBlockParamContentUnion{
584								{
585									OfText: &anthropic.TextBlockParam{
586										Text: content.Error.Error(),
587									},
588								},
589							}
590							toolResultBlock.IsError = param.NewOpt(true)
591						}
592						if cacheControl != nil {
593							toolResultBlock.CacheControl = anthropic.NewCacheControlEphemeralParam()
594						}
595						anthropicContent = append(anthropicContent, anthropic.ContentBlockParamUnion{
596							OfToolResult: &toolResultBlock,
597						})
598					}
599				}
600			}
601			messages = append(messages, anthropic.NewUserMessage(anthropicContent...))
602		case fantasy.MessageRoleAssistant:
603			var anthropicContent []anthropic.ContentBlockParamUnion
604			for _, msg := range block.Messages {
605				for i, part := range msg.Content {
606					isLastPart := i == len(msg.Content)-1
607					cacheControl := getCacheControl(part.Options())
608					if cacheControl == nil && isLastPart {
609						cacheControl = getCacheControl(msg.ProviderOptions)
610					}
611					switch part.GetType() {
612					case fantasy.ContentTypeText:
613						text, ok := fantasy.AsMessagePart[fantasy.TextPart](part)
614						if !ok {
615							continue
616						}
617						textBlock := &anthropic.TextBlockParam{
618							Text: text.Text,
619						}
620						if cacheControl != nil {
621							textBlock.CacheControl = anthropic.NewCacheControlEphemeralParam()
622						}
623						anthropicContent = append(anthropicContent, anthropic.ContentBlockParamUnion{
624							OfText: textBlock,
625						})
626					case fantasy.ContentTypeReasoning:
627						reasoning, ok := fantasy.AsMessagePart[fantasy.ReasoningPart](part)
628						if !ok {
629							continue
630						}
631						if !sendReasoningData {
632							warnings = append(warnings, fantasy.CallWarning{
633								Type:    "other",
634								Message: "sending reasoning content is disabled for this model",
635							})
636							continue
637						}
638						reasoningMetadata := getReasoningMetadata(part.Options())
639						if reasoningMetadata == nil {
640							warnings = append(warnings, fantasy.CallWarning{
641								Type:    "other",
642								Message: "unsupported reasoning metadata",
643							})
644							continue
645						}
646
647						if reasoningMetadata.Signature != "" {
648							anthropicContent = append(anthropicContent, anthropic.NewThinkingBlock(reasoningMetadata.Signature, reasoning.Text))
649						} else if reasoningMetadata.RedactedData != "" {
650							anthropicContent = append(anthropicContent, anthropic.NewRedactedThinkingBlock(reasoningMetadata.RedactedData))
651						} else {
652							warnings = append(warnings, fantasy.CallWarning{
653								Type:    "other",
654								Message: "unsupported reasoning metadata",
655							})
656							continue
657						}
658					case fantasy.ContentTypeToolCall:
659						toolCall, ok := fantasy.AsMessagePart[fantasy.ToolCallPart](part)
660						if !ok {
661							continue
662						}
663						if toolCall.ProviderExecuted {
664							// TODO: implement provider executed call
665							continue
666						}
667
668						var inputMap map[string]any
669						err := json.Unmarshal([]byte(toolCall.Input), &inputMap)
670						if err != nil {
671							continue
672						}
673						toolUseBlock := anthropic.NewToolUseBlock(toolCall.ToolCallID, inputMap, toolCall.ToolName)
674						if cacheControl != nil {
675							toolUseBlock.OfToolUse.CacheControl = anthropic.NewCacheControlEphemeralParam()
676						}
677						anthropicContent = append(anthropicContent, toolUseBlock)
678					case fantasy.ContentTypeToolResult:
679						// TODO: implement provider executed tool result
680					}
681				}
682			}
683			messages = append(messages, anthropic.NewAssistantMessage(anthropicContent...))
684		}
685	}
686	return systemBlocks, messages, warnings
687}
688
689func (a languageModel) handleError(err error) error {
690	var apiErr *anthropic.Error
691	if errors.As(err, &apiErr) {
692		requestDump := apiErr.DumpRequest(true)
693		responseDump := apiErr.DumpResponse(true)
694		headers := map[string]string{}
695		for k, h := range apiErr.Response.Header {
696			v := h[len(h)-1]
697			headers[strings.ToLower(k)] = v
698		}
699		return fantasy.NewAPICallError(
700			apiErr.Error(),
701			apiErr.Request.URL.String(),
702			string(requestDump),
703			apiErr.StatusCode,
704			headers,
705			string(responseDump),
706			apiErr,
707			false,
708		)
709	}
710	return err
711}
712
713func mapFinishReason(finishReason string) fantasy.FinishReason {
714	switch finishReason {
715	case "end_turn", "pause_turn", "stop_sequence":
716		return fantasy.FinishReasonStop
717	case "max_tokens":
718		return fantasy.FinishReasonLength
719	case "tool_use":
720		return fantasy.FinishReasonToolCalls
721	default:
722		return fantasy.FinishReasonUnknown
723	}
724}
725
726// Generate implements fantasy.LanguageModel.
727func (a languageModel) Generate(ctx context.Context, call fantasy.Call) (*fantasy.Response, error) {
728	params, warnings, err := a.prepareParams(call)
729	if err != nil {
730		return nil, err
731	}
732	response, err := a.client.Messages.New(ctx, *params)
733	if err != nil {
734		return nil, a.handleError(err)
735	}
736
737	var content []fantasy.Content
738	for _, block := range response.Content {
739		switch block.Type {
740		case "text":
741			text, ok := block.AsAny().(anthropic.TextBlock)
742			if !ok {
743				continue
744			}
745			content = append(content, fantasy.TextContent{
746				Text: text.Text,
747			})
748		case "thinking":
749			reasoning, ok := block.AsAny().(anthropic.ThinkingBlock)
750			if !ok {
751				continue
752			}
753			content = append(content, fantasy.ReasoningContent{
754				Text: reasoning.Thinking,
755				ProviderMetadata: fantasy.ProviderMetadata{
756					Name: &ReasoningOptionMetadata{
757						Signature: reasoning.Signature,
758					},
759				},
760			})
761		case "redacted_thinking":
762			reasoning, ok := block.AsAny().(anthropic.RedactedThinkingBlock)
763			if !ok {
764				continue
765			}
766			content = append(content, fantasy.ReasoningContent{
767				Text: "",
768				ProviderMetadata: fantasy.ProviderMetadata{
769					Name: &ReasoningOptionMetadata{
770						RedactedData: reasoning.Data,
771					},
772				},
773			})
774		case "tool_use":
775			toolUse, ok := block.AsAny().(anthropic.ToolUseBlock)
776			if !ok {
777				continue
778			}
779			content = append(content, fantasy.ToolCallContent{
780				ToolCallID:       toolUse.ID,
781				ToolName:         toolUse.Name,
782				Input:            string(toolUse.Input),
783				ProviderExecuted: false,
784			})
785		}
786	}
787
788	return &fantasy.Response{
789		Content: content,
790		Usage: fantasy.Usage{
791			InputTokens:         response.Usage.InputTokens,
792			OutputTokens:        response.Usage.OutputTokens,
793			TotalTokens:         response.Usage.InputTokens + response.Usage.OutputTokens,
794			CacheCreationTokens: response.Usage.CacheCreationInputTokens,
795			CacheReadTokens:     response.Usage.CacheReadInputTokens,
796		},
797		FinishReason:     mapFinishReason(string(response.StopReason)),
798		ProviderMetadata: fantasy.ProviderMetadata{},
799		Warnings:         warnings,
800	}, nil
801}
802
803// Stream implements fantasy.LanguageModel.
804func (a languageModel) Stream(ctx context.Context, call fantasy.Call) (fantasy.StreamResponse, error) {
805	params, warnings, err := a.prepareParams(call)
806	if err != nil {
807		return nil, err
808	}
809
810	stream := a.client.Messages.NewStreaming(ctx, *params)
811	acc := anthropic.Message{}
812	return func(yield func(fantasy.StreamPart) bool) {
813		if len(warnings) > 0 {
814			if !yield(fantasy.StreamPart{
815				Type:     fantasy.StreamPartTypeWarnings,
816				Warnings: warnings,
817			}) {
818				return
819			}
820		}
821
822		for stream.Next() {
823			chunk := stream.Current()
824			_ = acc.Accumulate(chunk)
825			switch chunk.Type {
826			case "content_block_start":
827				contentBlockType := chunk.ContentBlock.Type
828				switch contentBlockType {
829				case "text":
830					if !yield(fantasy.StreamPart{
831						Type: fantasy.StreamPartTypeTextStart,
832						ID:   fmt.Sprintf("%d", chunk.Index),
833					}) {
834						return
835					}
836				case "thinking":
837					if !yield(fantasy.StreamPart{
838						Type: fantasy.StreamPartTypeReasoningStart,
839						ID:   fmt.Sprintf("%d", chunk.Index),
840					}) {
841						return
842					}
843				case "redacted_thinking":
844					if !yield(fantasy.StreamPart{
845						Type: fantasy.StreamPartTypeReasoningStart,
846						ID:   fmt.Sprintf("%d", chunk.Index),
847						ProviderMetadata: fantasy.ProviderMetadata{
848							Name: &ReasoningOptionMetadata{
849								RedactedData: chunk.ContentBlock.Data,
850							},
851						},
852					}) {
853						return
854					}
855				case "tool_use":
856					if !yield(fantasy.StreamPart{
857						Type:          fantasy.StreamPartTypeToolInputStart,
858						ID:            chunk.ContentBlock.ID,
859						ToolCallName:  chunk.ContentBlock.Name,
860						ToolCallInput: "",
861					}) {
862						return
863					}
864				}
865			case "content_block_stop":
866				if len(acc.Content)-1 < int(chunk.Index) {
867					continue
868				}
869				contentBlock := acc.Content[int(chunk.Index)]
870				switch contentBlock.Type {
871				case "text":
872					if !yield(fantasy.StreamPart{
873						Type: fantasy.StreamPartTypeTextEnd,
874						ID:   fmt.Sprintf("%d", chunk.Index),
875					}) {
876						return
877					}
878				case "thinking":
879					if !yield(fantasy.StreamPart{
880						Type: fantasy.StreamPartTypeReasoningEnd,
881						ID:   fmt.Sprintf("%d", chunk.Index),
882					}) {
883						return
884					}
885				case "tool_use":
886					if !yield(fantasy.StreamPart{
887						Type: fantasy.StreamPartTypeToolInputEnd,
888						ID:   contentBlock.ID,
889					}) {
890						return
891					}
892					if !yield(fantasy.StreamPart{
893						Type:          fantasy.StreamPartTypeToolCall,
894						ID:            contentBlock.ID,
895						ToolCallName:  contentBlock.Name,
896						ToolCallInput: string(contentBlock.Input),
897					}) {
898						return
899					}
900				}
901			case "content_block_delta":
902				switch chunk.Delta.Type {
903				case "text_delta":
904					if !yield(fantasy.StreamPart{
905						Type:  fantasy.StreamPartTypeTextDelta,
906						ID:    fmt.Sprintf("%d", chunk.Index),
907						Delta: chunk.Delta.Text,
908					}) {
909						return
910					}
911				case "thinking_delta":
912					if !yield(fantasy.StreamPart{
913						Type:  fantasy.StreamPartTypeReasoningDelta,
914						ID:    fmt.Sprintf("%d", chunk.Index),
915						Delta: chunk.Delta.Thinking,
916					}) {
917						return
918					}
919				case "signature_delta":
920					if !yield(fantasy.StreamPart{
921						Type: fantasy.StreamPartTypeReasoningDelta,
922						ID:   fmt.Sprintf("%d", chunk.Index),
923						ProviderMetadata: fantasy.ProviderMetadata{
924							Name: &ReasoningOptionMetadata{
925								Signature: chunk.Delta.Signature,
926							},
927						},
928					}) {
929						return
930					}
931				case "input_json_delta":
932					if len(acc.Content)-1 < int(chunk.Index) {
933						continue
934					}
935					contentBlock := acc.Content[int(chunk.Index)]
936					if !yield(fantasy.StreamPart{
937						Type:          fantasy.StreamPartTypeToolInputDelta,
938						ID:            contentBlock.ID,
939						ToolCallInput: chunk.Delta.PartialJSON,
940					}) {
941						return
942					}
943				}
944			case "message_stop":
945			}
946		}
947
948		err := stream.Err()
949		if err == nil || errors.Is(err, io.EOF) {
950			yield(fantasy.StreamPart{
951				Type:         fantasy.StreamPartTypeFinish,
952				ID:           acc.ID,
953				FinishReason: mapFinishReason(string(acc.StopReason)),
954				Usage: fantasy.Usage{
955					InputTokens:         acc.Usage.InputTokens,
956					OutputTokens:        acc.Usage.OutputTokens,
957					TotalTokens:         acc.Usage.InputTokens + acc.Usage.OutputTokens,
958					CacheCreationTokens: acc.Usage.CacheCreationInputTokens,
959					CacheReadTokens:     acc.Usage.CacheReadInputTokens,
960				},
961				ProviderMetadata: fantasy.ProviderMetadata{},
962			})
963			return
964		} else { //nolint: revive
965			yield(fantasy.StreamPart{
966				Type:  fantasy.StreamPartTypeError,
967				Error: a.handleError(err),
968			})
969			return
970		}
971	}, nil
972}