From 64ebfc010618034268272af465bb47dbbb49d64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matteo=20Calabr=C3=B2?= Date: Sun, 21 Sep 2025 16:52:10 +0200 Subject: [PATCH] feat(autofix): update gemini sdk and add anthropic claude * upgrade gemini sdk to google.golang.org/genai v1.25.0 * support newer gemini models * add anthropic claude --- autofix/ai.go | 113 +++++++++------------------------------------ autofix/ai_test.go | 45 +++++------------- autofix/claude.go | 74 +++++++++++++++++++++++++++++ autofix/gemini.go | 91 ++++++++++++++++++++++++++++++++++++ cmd/gosec/main.go | 12 ++--- go.mod | 16 +++---- go.sum | 36 +++++++-------- 7 files changed, 229 insertions(+), 158 deletions(-) create mode 100644 autofix/claude.go create mode 100644 autofix/gemini.go diff --git a/autofix/ai.go b/autofix/ai.go index 3535f7e..9e893d8 100644 --- a/autofix/ai.go +++ b/autofix/ai.go @@ -4,97 +4,53 @@ import ( "context" "errors" "fmt" + "strings" "time" - "github.com/google/generative-ai-go/genai" - "google.golang.org/api/option" - "github.com/securego/gosec/v2/issue" ) const ( - GeminiModel = "gemini-1.5-flash" - AIPrompt = `Provide a brief explanation and a solution to fix this security issue + AIProviderFlagHelp = `AI API provider to generate auto fixes to issues. Valid options are: +- gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite, gemini-2.0-flash, gemini-2.0-flash-lite (gemini, default); +- claude-sonnet-4-0 (claude, default), claude-opus-4-0, claude-opus-4-1, claude-sonnet-3-7` + + AIPrompt = `Provide a brief explanation and a solution to fix this security issue in Go programming language: %q. Answer in markdown format and keep the response limited to 200 words.` - GeminiProvider = "gemini" timeout = 30 * time.Second ) -// GenAIClient defines the interface for the GenAI client. type GenAIClient interface { - // Close clean up and close the client. - Close() error - // GenerativeModel build the generative mode. - GenerativeModel(name string) GenAIGenerativeModel + GenerateSolution(ctx context.Context, prompt string) (string, error) } -// GenAIGenerativeModel defines the interface for the Generative Model. -type GenAIGenerativeModel interface { - // GenerateContent generates an response for given prompt. - GenerateContent(ctx context.Context, prompt string) (string, error) -} +// GenerateSolution generates a solution for the given issues using the specified AI provider +func GenerateSolution(model, aiAPIKey string, issues []*issue.Issue) (err error) { + var client GenAIClient -// genAIClientWrapper wraps the genai.Client to implement GenAIClient. -type genAIClientWrapper struct { - client *genai.Client -} - -// Close closes the gen AI client. -func (w *genAIClientWrapper) Close() error { - return w.client.Close() -} - -// GenerativeModel builds the generative Model. -func (w *genAIClientWrapper) GenerativeModel(name string) GenAIGenerativeModel { - return &genAIGenerativeModelWrapper{model: w.client.GenerativeModel(name)} -} - -// genAIGenerativeModelWrapper wraps the genai.GenerativeModel to implement GenAIGenerativeModel -type genAIGenerativeModelWrapper struct { - // model is the underlying generative model - model *genai.GenerativeModel -} - -// GenerateContent generates a response for the given prompt using gemini API. -func (w *genAIGenerativeModelWrapper) GenerateContent(ctx context.Context, prompt string) (string, error) { - resp, err := w.model.GenerateContent(ctx, genai.Text(prompt)) - if err != nil { - return "", fmt.Errorf("generating autofix: %w", err) - } - if len(resp.Candidates) == 0 { - return "", errors.New("no autofix returned by gemini") + switch { + case strings.HasPrefix(model, "claude"): + client, err = NewClaudeClient(model, aiAPIKey) + case strings.HasPrefix(model, "gemini"): + client, err = NewGeminiClient(model, aiAPIKey) } - if len(resp.Candidates[0].Content.Parts) == 0 { - return "", errors.New("nothing found in the first autofix returned by gemini") + switch { + case err != nil: + return fmt.Errorf("initializing AI client: %w", err) + case client == nil: + return fmt.Errorf("unsupported AI backend: %s", model) } - // Return the first candidate - return fmt.Sprintf("%+v", resp.Candidates[0].Content.Parts[0]), nil + return generateSolution(client, issues) } -// NewGenAIClient creates a new gemini API client. -func NewGenAIClient(ctx context.Context, aiAPIKey, endpoint string) (GenAIClient, error) { - clientOptions := []option.ClientOption{option.WithAPIKey(aiAPIKey)} - if endpoint != "" { - clientOptions = append(clientOptions, option.WithEndpoint(endpoint)) - } - - client, err := genai.NewClient(ctx, clientOptions...) - if err != nil { - return nil, fmt.Errorf("calling gemini API: %w", err) - } - - return &genAIClientWrapper{client: client}, nil -} - -func generateSolutionByGemini(client GenAIClient, issues []*issue.Issue) error { +func generateSolution(client GenAIClient, issues []*issue.Issue) error { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - model := client.GenerativeModel(GeminiModel) cachedAutofix := make(map[string]string) for _, issue := range issues { if val, ok := cachedAutofix[issue.What]; ok { @@ -103,7 +59,7 @@ func generateSolutionByGemini(client GenAIClient, issues []*issue.Issue) error { } prompt := fmt.Sprintf(AIPrompt, issue.What) - resp, err := model.GenerateContent(ctx, prompt) + resp, err := client.GenerateSolution(ctx, prompt) if err != nil { return fmt.Errorf("generating autofix with gemini: %w", err) } @@ -117,26 +73,3 @@ func generateSolutionByGemini(client GenAIClient, issues []*issue.Issue) error { } return nil } - -// GenerateSolution generates a solution for the given issues using the specified AI provider -func GenerateSolution(aiAPIProvider, aiAPIKey, endpoint string, issues []*issue.Issue) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - var client GenAIClient - - switch aiAPIProvider { - case GeminiProvider: - var err error - client, err = NewGenAIClient(ctx, aiAPIKey, endpoint) - if err != nil { - return fmt.Errorf("generating autofix: %w", err) - } - default: - return errors.New("ai provider not supported") - } - - defer client.Close() - - return generateSolutionByGemini(client, issues) -} diff --git a/autofix/ai_test.go b/autofix/ai_test.go index 4fac6bf..72334f6 100644 --- a/autofix/ai_test.go +++ b/autofix/ai_test.go @@ -17,22 +17,7 @@ type MockGenAIClient struct { mock.Mock } -func (m *MockGenAIClient) Close() error { - args := m.Called() - return args.Error(0) -} - -func (m *MockGenAIClient) GenerativeModel(name string) GenAIGenerativeModel { - args := m.Called(name) - return args.Get(0).(GenAIGenerativeModel) -} - -// MockGenAIGenerativeModel is a mock of the GenAIGenerativeModel interface -type MockGenAIGenerativeModel struct { - mock.Mock -} - -func (m *MockGenAIGenerativeModel) GenerateContent(ctx context.Context, prompt string) (string, error) { +func (m *MockGenAIClient) GenerateSolution(ctx context.Context, prompt string) (string, error) { args := m.Called(ctx, prompt) return args.String(0), args.Error(1) } @@ -44,17 +29,15 @@ func TestGenerateSolutionByGemini_Success(t *testing.T) { } mockClient := new(MockGenAIClient) - mockModel := new(MockGenAIGenerativeModel) - mockClient.On("GenerativeModel", GeminiModel).Return(mockModel).Once() - mockModel.On("GenerateContent", mock.Anything, mock.Anything).Return("Autofix for issue 1", nil).Once() + mockClient.On("GenerateSolution", mock.Anything, mock.Anything).Return("Autofix for issue 1", nil).Once() // Act - err := generateSolutionByGemini(mockClient, issues) + err := generateSolution(mockClient, issues) // Assert require.NoError(t, err) assert.Equal(t, []*issue.Issue{{What: "Example issue 1", Autofix: "Autofix for issue 1"}}, issues) - mock.AssertExpectationsForObjects(t, mockClient, mockModel) + mock.AssertExpectationsForObjects(t, mockClient) } func TestGenerateSolutionByGemini_NoCandidates(t *testing.T) { @@ -64,16 +47,14 @@ func TestGenerateSolutionByGemini_NoCandidates(t *testing.T) { } mockClient := new(MockGenAIClient) - mockModel := new(MockGenAIGenerativeModel) - mockClient.On("GenerativeModel", GeminiModel).Return(mockModel).Once() - mockModel.On("GenerateContent", mock.Anything, mock.Anything).Return("", nil).Once() + mockClient.On("GenerateSolution", mock.Anything, mock.Anything).Return("", nil).Once() // Act - err := generateSolutionByGemini(mockClient, issues) + err := generateSolution(mockClient, issues) // Assert require.EqualError(t, err, "no autofix returned by gemini") - mock.AssertExpectationsForObjects(t, mockClient, mockModel) + mock.AssertExpectationsForObjects(t, mockClient) } func TestGenerateSolutionByGemini_APIError(t *testing.T) { @@ -83,16 +64,14 @@ func TestGenerateSolutionByGemini_APIError(t *testing.T) { } mockClient := new(MockGenAIClient) - mockModel := new(MockGenAIGenerativeModel) - mockClient.On("GenerativeModel", GeminiModel).Return(mockModel).Once() - mockModel.On("GenerateContent", mock.Anything, mock.Anything).Return("", errors.New("API error")).Once() + mockClient.On("GenerateSolution", mock.Anything, mock.Anything).Return("", errors.New("API error")).Once() // Act - err := generateSolutionByGemini(mockClient, issues) + err := generateSolution(mockClient, issues) // Assert require.EqualError(t, err, "generating autofix with gemini: API error") - mock.AssertExpectationsForObjects(t, mockClient, mockModel) + mock.AssertExpectationsForObjects(t, mockClient) } func TestGenerateSolution_UnsupportedProvider(t *testing.T) { @@ -102,8 +81,8 @@ func TestGenerateSolution_UnsupportedProvider(t *testing.T) { } // Act - err := GenerateSolution("unsupported-provider", "test-api-key", "", issues) + err := GenerateSolution("unsupported-provider", "test-api-key", issues) // Assert - require.EqualError(t, err, "ai provider not supported") + require.EqualError(t, err, "unsupported AI backend: unsupported-provider") } diff --git a/autofix/claude.go b/autofix/claude.go new file mode 100644 index 0000000..66da800 --- /dev/null +++ b/autofix/claude.go @@ -0,0 +1,74 @@ +package autofix + +import ( + "context" + "errors" + "fmt" + + "github.com/anthropics/anthropic-sdk-go" + "github.com/anthropics/anthropic-sdk-go/option" +) + +const ( + ModelClaudeOpus4_0 = anthropic.ModelClaudeOpus4_0 + ModelClaudeOpus4_1 = anthropic.ModelClaudeOpus4_1_20250805 + ModelClaudeSonnet4_0 = anthropic.ModelClaudeSonnet4_0 +) + +var _ GenAIClient = (*claudeWrapper)(nil) + +type claudeWrapper struct { + client anthropic.Client + model anthropic.Model +} + +func NewClaudeClient(model, apiKey string) (GenAIClient, error) { + var options []option.RequestOption + + if apiKey != "" { + options = append(options, option.WithAPIKey(apiKey)) + } + + anthropicModel := parseAnthropicModel(model) + + return &claudeWrapper{ + client: anthropic.NewClient(options...), + model: anthropicModel, + }, nil +} + +func (c *claudeWrapper) GenerateSolution(ctx context.Context, prompt string) (string, error) { + resp, err := c.client.Messages.New(ctx, anthropic.MessageNewParams{ + Model: anthropic.Model(c.model), + MaxTokens: 1024, + Messages: []anthropic.MessageParam{ + anthropic.NewUserMessage(anthropic.NewTextBlock(prompt)), + }, + }) + if err != nil { + return "", fmt.Errorf("generating autofix: %w", err) + } + + if resp == nil || len(resp.Content) == 0 { + return "", errors.New("no autofix returned by claude") + } + + if len(resp.Content[0].Text) == 0 { + return "", errors.New("nothing found in the first autofix returned by claude") + } + + return resp.Content[0].Text, nil +} + +func parseAnthropicModel(model string) anthropic.Model { + switch model { + case "claude-sonnet-3-7": + return anthropic.ModelClaude3_7SonnetLatest + case "claude-opus", "claude-opus-4-0": + return anthropic.ModelClaudeOpus4_0 + case "claude-opus-4-1": + return anthropic.ModelClaudeOpus4_1_20250805 + } + + return anthropic.ModelClaudeSonnet4_0 +} diff --git a/autofix/gemini.go b/autofix/gemini.go new file mode 100644 index 0000000..aa1a74c --- /dev/null +++ b/autofix/gemini.go @@ -0,0 +1,91 @@ +package autofix + +import ( + "context" + "errors" + "fmt" + + "google.golang.org/genai" +) + +// https://ai.google.dev/gemini-api/docs/models +type GenAIModel string + +const ( + ModelGeminiPro2_5 GenAIModel = "gemini-2.5-pro" + ModelGeminiFlash2_5 GenAIModel = "gemini-2.5-flash" + ModelGeminiFlash2_5Lite GenAIModel = "gemini-2.5-flash-lite" + ModelGeminiFlash2_0 GenAIModel = "gemini-2.0-flash" + ModelGeminiFlash2_0Lite GenAIModel = "gemini-2.0-flash-lite" + // Deprecated: Use Gemini 2.x models. + ModelGeminiFlash1_5 GenAIModel = "gemini-1.5-flash" +) + +var _ GenAIClient = (*geminiWrapper)(nil) + +type geminiWrapper struct { + client *genai.Client + model GenAIModel +} + +func NewGeminiClient(model, apiKey string) (GenAIClient, error) { + ctx := context.Background() + + genaiModel, err := parseGeminiModel(model) + if err != nil { + return nil, err + } + + config := genai.ClientConfig{ + APIKey: apiKey, + Backend: genai.BackendUnspecified, + } + + client, err := genai.NewClient(ctx, &config) + if err != nil { + return nil, fmt.Errorf("creating gemini client: %w", err) + } + + return &geminiWrapper{ + client: client, + model: genaiModel, + }, nil +} + +func (g *geminiWrapper) GenerateSolution(ctx context.Context, prompt string) (string, error) { + var config genai.GenerateContentConfig + + resp, err := g.client.Models.GenerateContent(ctx, string(g.model), genai.Text(prompt), &config) + if err != nil { + return "", fmt.Errorf("generating autofix: %w", err) + } + + if resp == nil || len(resp.Candidates) == 0 { + return "", errors.New("no autofix returned by gemini") + } + + if len(resp.Candidates[0].Content.Parts) == 0 { + return "", errors.New("nothing found in the first autofix returned by gemini") + } + + return resp.Text(), nil +} + +func parseGeminiModel(model string) (GenAIModel, error) { + switch model { + case "gemini-2.5-pro": + return ModelGeminiPro2_5, nil + case "gemini-2.5-flash": + return ModelGeminiFlash2_5, nil + case "gemini-2.5-flash-lite": + return ModelGeminiFlash2_5Lite, nil + case "gemini-2.0-flash": + return ModelGeminiFlash2_0, nil + case "gemini-2.0-flash-lite", "gemini": // Default + return ModelGeminiFlash2_0Lite, nil + case "gemini-1.5-flash": + return ModelGeminiFlash1_5, nil + } + + return "", fmt.Errorf("unsupported gemini model: %s", model) +} diff --git a/cmd/gosec/main.go b/cmd/gosec/main.go index 3154fab..63e0ffd 100644 --- a/cmd/gosec/main.go +++ b/cmd/gosec/main.go @@ -154,14 +154,11 @@ var ( flagTerse = flag.Bool("terse", false, "Shows only the results and summary") // AI platform provider to generate solutions to issues - flagAiAPIProvider = flag.String("ai-api-provider", "", "AI API provider to generate auto fixes to issues.\nValid options are: gemini") + flagAiAPIProvider = flag.String("ai-api-provider", "", autofix.AIProviderFlagHelp) // key to implementing AI provider services flagAiAPIKey = flag.String("ai-api-key", "", "Key to access the AI API") - // endpoint to the AI provider - flagAiEndpoint = flag.String("ai-endpoint", "", "Endpoint AI API.\nThis is optional, the default API endpoint will be used when not provided.") - // exclude the folders from scan flagDirsExclude arrayFlags @@ -508,8 +505,11 @@ func main() { if aiAPIKey == "" { aiAPIKey = *flagAiAPIKey } - if *flagAiAPIProvider != "" && aiAPIKey != "" { - err := autofix.GenerateSolution(*flagAiAPIProvider, aiAPIKey, *flagAiEndpoint, issues) + + aiEnabled := *flagAiAPIProvider != "" + + if len(issues) > 0 && aiEnabled { + err := autofix.GenerateSolution(*flagAiAPIProvider, aiAPIKey, issues) if err != nil { logger.Print(err) } diff --git a/go.mod b/go.mod index 61d809c..d889941 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/securego/gosec/v2 require ( + github.com/anthropics/anthropic-sdk-go v1.12.0 github.com/ccojocar/zxcvbn-go v1.0.4 - github.com/google/generative-ai-go v0.20.1 github.com/google/uuid v1.6.0 github.com/gookit/color v1.6.0 github.com/lib/pq v1.10.9 @@ -13,17 +13,14 @@ require ( golang.org/x/crypto v0.42.0 golang.org/x/text v0.29.0 golang.org/x/tools v0.37.0 - google.golang.org/api v0.249.0 + google.golang.org/genai v1.25.0 gopkg.in/yaml.v3 v3.0.1 ) require ( cloud.google.com/go v0.121.2 // indirect - cloud.google.com/go/ai v0.12.1 // indirect cloud.google.com/go/auth v0.16.5 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.8.0 // indirect - cloud.google.com/go/longrunning v0.6.7 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -35,11 +32,15 @@ require ( github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect @@ -48,11 +49,8 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.28.0 // indirect golang.org/x/net v0.44.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect - golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect google.golang.org/grpc v1.75.0 // indirect google.golang.org/protobuf v1.36.8 // indirect diff --git a/go.sum b/go.sum index 2dbe13b..bb56e72 100644 --- a/go.sum +++ b/go.sum @@ -15,12 +15,8 @@ cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZ cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg= cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= -cloud.google.com/go/ai v0.12.1 h1:m1n/VjUuHS+pEO/2R4/VbuuEIkgk0w67fDQvFaMngM0= -cloud.google.com/go/ai v0.12.1/go.mod h1:5vIPNe1ZQsVZqCliXIPL4QnhObQQY4d9hAGHdVc4iw4= cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= -cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= -cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -31,8 +27,6 @@ cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcao cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -58,6 +52,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/anthropics/anthropic-sdk-go v1.12.0 h1:xPqlGnq7rWrTiHazIvCiumA0u7mGQnwDQtvA1M82h9U= +github.com/anthropics/anthropic-sdk-go v1.12.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -162,8 +158,6 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= -github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ= -github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -207,6 +201,8 @@ github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b0 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= @@ -358,6 +354,16 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -387,8 +393,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= @@ -499,8 +503,6 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -569,8 +571,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -640,8 +640,6 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.249.0 h1:0VrsWAKzIZi058aeq+I86uIXbNhm9GxSHpbmZ92a38w= -google.golang.org/api v0.249.0/go.mod h1:dGk9qyI0UYPwO/cjt2q06LG/EhUpwZGdAbYF14wHHrQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -649,6 +647,8 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genai v1.25.0 h1:Cpyh2nmEoOS1eM3mT9XKuA/qWTEDoktfP2gsN3EduPE= +google.golang.org/genai v1.25.0/go.mod h1:OClfdf+r5aaD+sCd4aUSkPzJItmg2wD/WON9lQnRPaY= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -680,10 +680,6 @@ google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1m google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=