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