package analyze import ( "context" "fmt" "strings" "github.com/anthropics/anthropic-sdk-go" "gitea.konchin.com/service/stonks/internal/config" ) func init() { Register("api", newAPIAnalyzer) } const defaultAPIModel = anthropic.ModelClaudeOpus5 // apiAnalyzer calls the Claude Messages API directly, billed per token. // Reads credentials the standard way (ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN // / an `ant auth login` profile) via the zero-arg client constructor. type apiAnalyzer struct { client anthropic.Client model anthropic.Model effort anthropic.OutputConfigEffort } func newAPIAnalyzer(cfg config.AnalyzerConfig) (Analyzer, error) { model := anthropic.Model(cfg.Model) if cfg.Model == "" { model = defaultAPIModel } effort := anthropic.OutputConfigEffortMedium if cfg.Effort != "" { effort = anthropic.OutputConfigEffort(cfg.Effort) } return &apiAnalyzer{ client: anthropic.NewClient(), model: model, effort: effort, }, nil } func (a *apiAnalyzer) Analyze(ctx context.Context, prompt string) (string, error) { resp, err := a.client.Messages.New(ctx, anthropic.MessageNewParams{ Model: a.model, MaxTokens: 4096, System: []anthropic.TextBlockParam{{Text: SystemPrompt}}, OutputConfig: anthropic.OutputConfigParam{ Effort: a.effort, }, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock(prompt)), }, }) if err != nil { return "", fmt.Errorf("analyze (api mode): %w", err) } if resp.StopReason == anthropic.StopReasonRefusal { return "", fmt.Errorf("analyze (api mode): request refused by safety classifiers") } var out strings.Builder for _, block := range resp.Content { if text, ok := block.AsAny().(anthropic.TextBlock); ok { out.WriteString(text.Text) } } if out.Len() == 0 { return "", fmt.Errorf("analyze (api mode): response contained no text content (stop_reason=%s)", resp.StopReason) } return out.String(), nil }