diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..db13c01 --- /dev/null +++ b/LICENSE @@ -0,0 +1,101 @@ +Business Source License 1.1 + +License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Parameters + +Licensor: MarketAlly LLC +Licensed Work: GitCaddy AI Service + The Licensed Work is (c) 2026 MarketAlly LLC + +Additional Use Grant: You may make use of the Licensed Work, provided that + you do not use the Licensed Work for an AI Service. + + An "AI Service" is a commercial offering that allows + third parties (other than your employees and contractors + acting on your behalf) to access the functionality of + the Licensed Work by means of AI-powered code + intelligence features. + +Change Date: Four years from the date the Licensed Work is published. + +Change License: MIT License + +----------------------------------------------------------------------------- + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +MarketAlly LLC hereby grants you permission to use this License's text to +license your works, and to refer to it using the trademark "Business Source +License", as long as you comply with the Covenants of Licensor below. + +----------------------------------------------------------------------------- + +Covenants of Licensor + +In consideration of the right to use this License's text and the "Business +Source License" name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the MIT License, or a license + that is no less permissive than the MIT License, as the Change License. + +2. To either: (a) specify an Additional Use Grant that does not impose any + additional restriction on use of the Licensed Work beyond those set forth + in this License, or (b) insert the text "None" to indicate no Additional + Use Grant. + +3. To specify a Change Date. + +4. Not to modify this License in any other way. + +----------------------------------------------------------------------------- + +Notice + +The Business Source License (this document, or the "License") is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c196e73 --- /dev/null +++ b/Makefile @@ -0,0 +1,91 @@ +# GitCaddy AI Service Makefile +# Copyright 2026 MarketAlly. All rights reserved. + +.PHONY: all build run test clean docker proto + +# Default target +all: build + +# Build the service +build: + dotnet build GitCaddy.AI.sln -c Release + +# Run the service in development mode +run: + cd src/GitCaddy.AI.Service && dotnet run + +# Run tests +test: + dotnet test GitCaddy.AI.sln + +# Clean build artifacts +clean: + dotnet clean GitCaddy.AI.sln + rm -rf src/*/bin src/*/obj + rm -rf go/gitcaddy-ai-client/proto/*.pb.go + +# Build Docker image +docker: + docker build -t gitcaddy/gitcaddy-ai:latest . + +# Run with Docker Compose +docker-up: + docker-compose up -d + +# Stop Docker Compose +docker-down: + docker-compose down + +# Generate Go protobuf files +proto: + cd go/gitcaddy-ai-client && go generate + +# Install development dependencies +deps: + dotnet restore GitCaddy.AI.sln + cd go/gitcaddy-ai-client && go mod download + +# Format code +fmt: + dotnet format GitCaddy.AI.sln + cd go/gitcaddy-ai-client && go fmt ./... + +# Lint code +lint: + dotnet format GitCaddy.AI.sln --verify-no-changes + cd go/gitcaddy-ai-client && go vet ./... + +# Run example (dotnet) +example-dotnet: + cd examples/dotnet && dotnet run + +# Run example (go) +example-go: + cd examples/go && go run main.go + +# Create a release +release: + @echo "Creating release..." + dotnet publish src/GitCaddy.AI.Service/GitCaddy.AI.Service.csproj -c Release -o dist/ + @echo "Release created in dist/" + +# Show help +help: + @echo "GitCaddy AI Service" + @echo "" + @echo "Usage: make [target]" + @echo "" + @echo "Targets:" + @echo " build Build the service" + @echo " run Run the service in development mode" + @echo " test Run tests" + @echo " clean Clean build artifacts" + @echo " docker Build Docker image" + @echo " docker-up Start with Docker Compose" + @echo " docker-down Stop Docker Compose" + @echo " proto Generate Go protobuf files" + @echo " deps Install dependencies" + @echo " fmt Format code" + @echo " lint Lint code" + @echo " release Create release artifacts" + @echo " help Show this help" diff --git a/README.md b/README.md new file mode 100644 index 0000000..4677642 --- /dev/null +++ b/README.md @@ -0,0 +1,234 @@ +# GitCaddy AI Service + +AI-powered code intelligence service for GitCaddy. Provides code review, documentation generation, issue triage, and agentic workflows. + +## Features + +- **Code Review**: AI-powered pull request and commit reviews with security analysis +- **Code Intelligence**: Explain code, suggest fixes, summarize changes +- **Issue Management**: Auto-triage issues, suggest labels, generate responses +- **Documentation**: Generate docs for code, commit messages +- **Agentic Workflows**: Multi-step AI workflows for complex tasks +- **Chat Interface**: Interactive AI assistant for developers + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ GitCaddy Server (Go) │ +│ └── AI Client (gRPC) │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ GitCaddy AI Service (.NET 9) │ │ +│ │ ├── gRPC API │ │ +│ │ ├── MarketAlly.AIPlugin (Multi-provider AI) │ │ +│ │ └── License Validation │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Quick Start + +### Prerequisites + +- .NET 9.0 SDK +- Docker (optional) +- API key for Claude, OpenAI, or Gemini + +### Development + +```bash +# Clone the repository +git clone https://git.marketally.com/gitcaddy/gitcaddy-ai.git +cd gitcaddy-ai + +# Set environment variables +export Providers__Claude__ApiKey="your-api-key" + +# Run the service +cd src/GitCaddy.AI.Service +dotnet run +``` + +### Docker + +```bash +# Set environment variables +export CLAUDE_API_KEY="your-api-key" +export GITCADDY_AI_LICENSE="your-license-key" + +# Run with Docker Compose +docker-compose up -d +``` + +## Configuration + +Configuration is done via `appsettings.json` or environment variables: + +```json +{ + "AIService": { + "DefaultProvider": "Claude", + "DefaultModel": "claude-sonnet-4-20250514", + "MaxTokens": 4096, + "Temperature": 0.7 + }, + "Providers": { + "Claude": { + "ApiKey": "sk-ant-...", + "Enabled": true + }, + "OpenAI": { + "ApiKey": "sk-...", + "Enabled": true + } + }, + "License": { + "LicenseKey": "your-license-key" + } +} +``` + +Environment variable format: `AIService__DefaultProvider=Claude` + +## API Reference + +The service exposes a gRPC API defined in `protos/gitcaddy_ai.proto`. + +### Code Review + +```protobuf +rpc ReviewPullRequest(ReviewPullRequestRequest) returns (ReviewPullRequestResponse); +rpc ReviewCommit(ReviewCommitRequest) returns (ReviewCommitResponse); +``` + +### Code Intelligence + +```protobuf +rpc SummarizeChanges(SummarizeChangesRequest) returns (SummarizeChangesResponse); +rpc ExplainCode(ExplainCodeRequest) returns (ExplainCodeResponse); +rpc SuggestFix(SuggestFixRequest) returns (SuggestFixResponse); +``` + +### Issue Management + +```protobuf +rpc TriageIssue(TriageIssueRequest) returns (TriageIssueResponse); +rpc SuggestLabels(SuggestLabelsRequest) returns (SuggestLabelsResponse); +rpc GenerateIssueResponse(GenerateIssueResponseRequest) returns (GenerateIssueResponseResponse); +``` + +### Documentation + +```protobuf +rpc GenerateDocumentation(GenerateDocumentationRequest) returns (GenerateDocumentationResponse); +rpc GenerateCommitMessage(GenerateCommitMessageRequest) returns (GenerateCommitMessageResponse); +``` + +### Workflows & Chat + +```protobuf +rpc StartWorkflow(StartWorkflowRequest) returns (stream WorkflowEvent); +rpc ExecuteTask(ExecuteTaskRequest) returns (ExecuteTaskResponse); +rpc Chat(stream ChatRequest) returns (stream ChatResponse); +``` + +## Client Libraries + +### .NET Client + +```csharp +using GitCaddy.AI.Client; + +var client = new GitCaddyAIClient("http://localhost:5051"); + +var response = await client.ReviewPullRequestAsync(new ReviewPullRequestRequest +{ + RepoId = 1, + PullRequestId = 42, + PrTitle = "Add feature X", + Files = { new FileDiff { Path = "src/foo.cs", Patch = "..." } } +}); + +Console.WriteLine(response.Summary); +``` + +### Go Client + +```go +import ai "git.marketally.com/gitcaddy/gitcaddy-ai/go/gitcaddy-ai-client" + +client, err := ai.NewClient("localhost:5051") +if err != nil { + log.Fatal(err) +} +defer client.Close() + +resp, err := client.ReviewPullRequest(ctx, &pb.ReviewPullRequestRequest{ + RepoId: 1, + PullRequestId: 42, + PrTitle: "Add feature X", + Files: []*pb.FileDiff{{Path: "src/foo.go", Patch: "..."}}, +}) +``` + +## Licensing + +GitCaddy AI is licensed under the Business Source License 1.1 (BSL-1.1). + +### License Tiers + +| Tier | Features | +|------|----------| +| **Standard** | Code review, code intelligence, documentation, chat | +| **Professional** | + Issue management, agentic workflows | +| **Enterprise** | + Custom models, audit logging, SSO integration | + +### Trial Mode + +Without a license key, the service runs in trial mode with Standard tier features for 30 days. + +## Development + +### Building + +```bash +dotnet build +``` + +### Testing + +```bash +dotnet test +``` + +### Generating Proto Files + +For Go client: +```bash +cd go/gitcaddy-ai-client +go generate +``` + +## Integration with GitCaddy Server + +Add the AI client to your GitCaddy server configuration: + +```ini +[ai] +ENABLED = true +SERVICE_URL = http://localhost:5051 +``` + +## Support + +- Documentation: https://docs.gitcaddy.com/ai +- Issues: https://git.marketally.com/gitcaddy/gitcaddy-ai/issues +- Email: support@marketally.com + +## License + +Copyright 2026 MarketAlly. All rights reserved. + +Licensed under the Business Source License 1.1 (BSL-1.1). diff --git a/examples/dotnet/Example.csproj b/examples/dotnet/Example.csproj new file mode 100644 index 0000000..f27b994 --- /dev/null +++ b/examples/dotnet/Example.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/examples/dotnet/Program.cs b/examples/dotnet/Program.cs new file mode 100644 index 0000000..1ea6d1f --- /dev/null +++ b/examples/dotnet/Program.cs @@ -0,0 +1,163 @@ +// Copyright 2026 MarketAlly. All rights reserved. +// SPDX-License-Identifier: BSL-1.1 + +// Example: Using GitCaddy AI Client in .NET + +using GitCaddy.AI.Client; +using GitCaddy.AI.Proto; + +Console.WriteLine("GitCaddy AI Client Example"); +Console.WriteLine("==========================\n"); + +// Create client +using var client = new GitCaddyAIClient("http://localhost:5051"); + +// Check health +Console.WriteLine("Checking service health..."); +var health = await client.CheckHealthAsync(); +Console.WriteLine($"Service healthy: {health.Healthy}"); +Console.WriteLine($"Version: {health.Version}"); +if (health.License != null) +{ + Console.WriteLine($"License: {health.License.Tier} tier for {health.License.Customer}"); +} +Console.WriteLine(); + +// Example 1: Code Review +Console.WriteLine("Example 1: Reviewing a Pull Request"); +Console.WriteLine("------------------------------------"); +var reviewResponse = await client.ReviewPullRequestAsync(new ReviewPullRequestRequest +{ + RepoId = 1, + PullRequestId = 42, + PrTitle = "Add user authentication", + PrDescription = "Implements JWT-based authentication for the API", + BaseBranch = "main", + HeadBranch = "feature/auth", + Files = + { + new FileDiff + { + Path = "src/auth/jwt.go", + Status = "added", + Language = "go", + Patch = @" ++package auth ++ ++import ""github.com/golang-jwt/jwt/v5"" ++ ++func GenerateToken(userID string) (string, error) { ++ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ ++ ""user_id"": userID, ++ }) ++ return token.SignedString([]byte(""secret"")) ++} +" + } + }, + Options = new ReviewOptions + { + CheckSecurity = true, + CheckPerformance = true, + SuggestImprovements = true + } +}); + +Console.WriteLine($"Verdict: {reviewResponse.Verdict}"); +Console.WriteLine($"Summary: {reviewResponse.Summary[..Math.Min(500, reviewResponse.Summary.Length)]}...\n"); + +// Example 2: Explain Code +Console.WriteLine("Example 2: Explaining Code"); +Console.WriteLine("---------------------------"); +var explainResponse = await client.ExplainCodeAsync(new ExplainCodeRequest +{ + RepoId = 1, + FilePath = "src/utils/retry.go", + Code = @" +func Retry(attempts int, sleep time.Duration, f func() error) error { + var err error + for i := 0; i < attempts; i++ { + if err = f(); err == nil { + return nil + } + time.Sleep(sleep) + sleep *= 2 + } + return fmt.Errorf(""after %d attempts: %w"", attempts, err) +} +", + Question = "What pattern is this implementing?" +}); + +Console.WriteLine($"Explanation: {explainResponse.Explanation[..Math.Min(500, explainResponse.Explanation.Length)]}...\n"); + +// Example 3: Generate Commit Message +Console.WriteLine("Example 3: Generating Commit Message"); +Console.WriteLine("-------------------------------------"); +var commitResponse = await client.GenerateCommitMessageAsync(new GenerateCommitMessageRequest +{ + RepoId = 1, + Style = "conventional", + Files = + { + new FileDiff + { + Path = "src/api/handlers.go", + Status = "modified", + Patch = @" +@@ -45,6 +45,15 @@ func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { ++func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request) { ++ id := chi.URLParam(r, ""id"") ++ if err := h.userService.Delete(r.Context(), id); err != nil { ++ http.Error(w, err.Error(), http.StatusInternalServerError) ++ return ++ } ++ w.WriteHeader(http.StatusNoContent) ++} +" + } + } +}); + +Console.WriteLine($"Suggested message: {commitResponse.Message}"); +if (commitResponse.Alternatives.Count > 0) +{ + Console.WriteLine("Alternatives:"); + foreach (var alt in commitResponse.Alternatives) + { + Console.WriteLine($" - {alt}"); + } +} +Console.WriteLine(); + +// Example 4: Triage Issue +Console.WriteLine("Example 4: Triaging an Issue"); +Console.WriteLine("-----------------------------"); +var triageResponse = await client.TriageIssueAsync(new TriageIssueRequest +{ + RepoId = 1, + IssueId = 123, + Title = "App crashes when uploading large files", + Body = @" +When I try to upload a file larger than 100MB, the application crashes with an out of memory error. + +Steps to reproduce: +1. Go to upload page +2. Select a file > 100MB +3. Click upload +4. App crashes + +Expected: Should show an error or handle gracefully +Actual: Application crashes + +Environment: Windows 11, Chrome 120 +", + AvailableLabels = { "bug", "enhancement", "question", "documentation", "performance", "security", "crash" } +}); + +Console.WriteLine($"Priority: {triageResponse.Priority}"); +Console.WriteLine($"Category: {triageResponse.Category}"); +Console.WriteLine($"Suggested labels: {string.Join(", ", triageResponse.SuggestedLabels)}"); +Console.WriteLine($"Summary: {triageResponse.Summary[..Math.Min(300, triageResponse.Summary.Length)]}...\n"); + +Console.WriteLine("Done!"); diff --git a/examples/go/go.mod b/examples/go/go.mod new file mode 100644 index 0000000..427d0be --- /dev/null +++ b/examples/go/go.mod @@ -0,0 +1,7 @@ +module git.marketally.com/gitcaddy/gitcaddy-ai/examples/go + +go 1.23 + +require git.marketally.com/gitcaddy/gitcaddy-ai/go/gitcaddy-ai-client v0.0.0 + +replace git.marketally.com/gitcaddy/gitcaddy-ai/go/gitcaddy-ai-client => ../../go/gitcaddy-ai-client diff --git a/examples/go/main.go b/examples/go/main.go new file mode 100644 index 0000000..91bff29 --- /dev/null +++ b/examples/go/main.go @@ -0,0 +1,147 @@ +// Copyright 2026 MarketAlly. All rights reserved. +// SPDX-License-Identifier: BSL-1.1 + +// Example: Using GitCaddy AI Client in Go +package main + +import ( + "context" + "fmt" + "log" + "time" + + ai "git.marketally.com/gitcaddy/gitcaddy-ai/go/gitcaddy-ai-client" + pb "git.marketally.com/gitcaddy/gitcaddy-ai/go/gitcaddy-ai-client/proto" +) + +func main() { + fmt.Println("GitCaddy AI Client Example (Go)") + fmt.Println("================================\n") + + // Create client + client, err := ai.NewClient("localhost:5051") + if err != nil { + log.Fatalf("Failed to create client: %v", err) + } + defer client.Close() + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + // Check health + fmt.Println("Checking service health...") + health, err := client.CheckHealth(ctx) + if err != nil { + log.Fatalf("Health check failed: %v", err) + } + fmt.Printf("Service healthy: %v\n", health.Healthy) + fmt.Printf("Version: %s\n", health.Version) + if health.License != nil { + fmt.Printf("License: %s tier for %s\n", health.License.Tier, health.License.Customer) + } + fmt.Println() + + // Example 1: Code Review + fmt.Println("Example 1: Reviewing a Pull Request") + fmt.Println("------------------------------------") + reviewResp, err := client.ReviewPullRequest(ctx, &pb.ReviewPullRequestRequest{ + RepoId: 1, + PullRequestId: 42, + PrTitle: "Add user authentication", + PrDescription: "Implements JWT-based authentication for the API", + BaseBranch: "main", + HeadBranch: "feature/auth", + Files: []*pb.FileDiff{ + { + Path: "src/auth/jwt.go", + Status: "added", + Language: "go", + Patch: ` ++package auth ++ ++import "github.com/golang-jwt/jwt/v5" ++ ++func GenerateToken(userID string) (string, error) { ++ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ ++ "user_id": userID, ++ }) ++ return token.SignedString([]byte("secret")) ++} +`, + }, + }, + Options: &pb.ReviewOptions{ + CheckSecurity: true, + CheckPerformance: true, + SuggestImprovements: true, + }, + }) + if err != nil { + log.Printf("Review failed: %v", err) + } else { + fmt.Printf("Verdict: %v\n", reviewResp.Verdict) + summary := reviewResp.Summary + if len(summary) > 500 { + summary = summary[:500] + "..." + } + fmt.Printf("Summary: %s\n\n", summary) + } + + // Example 2: Generate Commit Message + fmt.Println("Example 2: Generating Commit Message") + fmt.Println("-------------------------------------") + commitResp, err := client.GenerateCommitMessage(ctx, &pb.GenerateCommitMessageRequest{ + RepoId: 1, + Style: "conventional", + Files: []*pb.FileDiff{ + { + Path: "src/api/handlers.go", + Status: "modified", + Patch: ` +@@ -45,6 +45,15 @@ func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { ++func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request) { ++ id := chi.URLParam(r, "id") ++ if err := h.userService.Delete(r.Context(), id); err != nil { ++ http.Error(w, err.Error(), http.StatusInternalServerError) ++ return ++ } ++ w.WriteHeader(http.StatusNoContent) ++} +`, + }, + }, + }) + if err != nil { + log.Printf("Commit message generation failed: %v", err) + } else { + fmt.Printf("Suggested message: %s\n", commitResp.Message) + if len(commitResp.Alternatives) > 0 { + fmt.Println("Alternatives:") + for _, alt := range commitResp.Alternatives { + fmt.Printf(" - %s\n", alt) + } + } + fmt.Println() + } + + // Example 3: Start a Workflow + fmt.Println("Example 3: Starting a Code Review Workflow") + fmt.Println("-------------------------------------------") + err = client.StartWorkflow(ctx, &pb.StartWorkflowRequest{ + RepoId: 1, + WorkflowType: "code_review", + Goal: "Review all changes in the auth module for security issues", + Parameters: map[string]string{ + "path": "src/auth/", + }, + }, func(event *pb.WorkflowEvent) error { + fmt.Printf("[%s] %s: %s\n", event.Type, event.AgentId, event.Message) + return nil + }) + if err != nil { + log.Printf("Workflow failed: %v", err) + } + fmt.Println() + + fmt.Println("Done!") +} diff --git a/go/gitcaddy-ai-client/client.go b/go/gitcaddy-ai-client/client.go new file mode 100644 index 0000000..48cced7 --- /dev/null +++ b/go/gitcaddy-ai-client/client.go @@ -0,0 +1,161 @@ +// Copyright 2026 MarketAlly. All rights reserved. +// SPDX-License-Identifier: BSL-1.1 + +// Package gitcaddyai provides a Go client for the GitCaddy AI Service. +package gitcaddyai + +import ( + "context" + "io" + + pb "git.marketally.com/gitcaddy/gitcaddy-ai/go/gitcaddy-ai-client/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +// Client provides access to GitCaddy AI Service. +type Client struct { + conn *grpc.ClientConn + client pb.GitCaddyAIClient +} + +// NewClient creates a new GitCaddy AI client. +func NewClient(address string, opts ...grpc.DialOption) (*Client, error) { + if len(opts) == 0 { + opts = []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} + } + + conn, err := grpc.NewClient(address, opts...) + if err != nil { + return nil, err + } + + return &Client{ + conn: conn, + client: pb.NewGitCaddyAIClient(conn), + }, nil +} + +// Close closes the client connection. +func (c *Client) Close() error { + return c.conn.Close() +} + +// ReviewPullRequest reviews a pull request and returns AI feedback. +func (c *Client) ReviewPullRequest(ctx context.Context, req *pb.ReviewPullRequestRequest) (*pb.ReviewPullRequestResponse, error) { + return c.client.ReviewPullRequest(ctx, req) +} + +// ReviewCommit reviews a single commit. +func (c *Client) ReviewCommit(ctx context.Context, req *pb.ReviewCommitRequest) (*pb.ReviewCommitResponse, error) { + return c.client.ReviewCommit(ctx, req) +} + +// SummarizeChanges summarizes code changes. +func (c *Client) SummarizeChanges(ctx context.Context, req *pb.SummarizeChangesRequest) (*pb.SummarizeChangesResponse, error) { + return c.client.SummarizeChanges(ctx, req) +} + +// ExplainCode explains a piece of code. +func (c *Client) ExplainCode(ctx context.Context, req *pb.ExplainCodeRequest) (*pb.ExplainCodeResponse, error) { + return c.client.ExplainCode(ctx, req) +} + +// SuggestFix suggests a fix for code with an error. +func (c *Client) SuggestFix(ctx context.Context, req *pb.SuggestFixRequest) (*pb.SuggestFixResponse, error) { + return c.client.SuggestFix(ctx, req) +} + +// TriageIssue triages an issue with priority and category. +func (c *Client) TriageIssue(ctx context.Context, req *pb.TriageIssueRequest) (*pb.TriageIssueResponse, error) { + return c.client.TriageIssue(ctx, req) +} + +// SuggestLabels suggests labels for an issue. +func (c *Client) SuggestLabels(ctx context.Context, req *pb.SuggestLabelsRequest) (*pb.SuggestLabelsResponse, error) { + return c.client.SuggestLabels(ctx, req) +} + +// GenerateIssueResponse generates a response to an issue. +func (c *Client) GenerateIssueResponse(ctx context.Context, req *pb.GenerateIssueResponseRequest) (*pb.GenerateIssueResponseResponse, error) { + return c.client.GenerateIssueResponse(ctx, req) +} + +// GenerateDocumentation generates documentation for code. +func (c *Client) GenerateDocumentation(ctx context.Context, req *pb.GenerateDocumentationRequest) (*pb.GenerateDocumentationResponse, error) { + return c.client.GenerateDocumentation(ctx, req) +} + +// GenerateCommitMessage generates a commit message from changes. +func (c *Client) GenerateCommitMessage(ctx context.Context, req *pb.GenerateCommitMessageRequest) (*pb.GenerateCommitMessageResponse, error) { + return c.client.GenerateCommitMessage(ctx, req) +} + +// ExecuteTask executes a single task. +func (c *Client) ExecuteTask(ctx context.Context, req *pb.ExecuteTaskRequest) (*pb.ExecuteTaskResponse, error) { + return c.client.ExecuteTask(ctx, req) +} + +// CheckHealth checks service health. +func (c *Client) CheckHealth(ctx context.Context) (*pb.HealthCheckResponse, error) { + return c.client.CheckHealth(ctx, &pb.HealthCheckRequest{}) +} + +// ValidateLicense validates a license key. +func (c *Client) ValidateLicense(ctx context.Context, licenseKey string) (*pb.ValidateLicenseResponse, error) { + return c.client.ValidateLicense(ctx, &pb.ValidateLicenseRequest{LicenseKey: licenseKey}) +} + +// WorkflowEventHandler handles workflow events from StartWorkflow. +type WorkflowEventHandler func(event *pb.WorkflowEvent) error + +// StartWorkflow starts a workflow and calls the handler for each event. +func (c *Client) StartWorkflow(ctx context.Context, req *pb.StartWorkflowRequest, handler WorkflowEventHandler) error { + stream, err := c.client.StartWorkflow(ctx, req) + if err != nil { + return err + } + + for { + event, err := stream.Recv() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + + if err := handler(event); err != nil { + return err + } + } +} + +// ChatSession represents an active chat session. +type ChatSession struct { + stream pb.GitCaddyAI_ChatClient +} + +// StartChat starts a new chat session. +func (c *Client) StartChat(ctx context.Context) (*ChatSession, error) { + stream, err := c.client.Chat(ctx) + if err != nil { + return nil, err + } + return &ChatSession{stream: stream}, nil +} + +// Send sends a message in the chat session. +func (s *ChatSession) Send(req *pb.ChatRequest) error { + return s.stream.Send(req) +} + +// Recv receives a response from the chat session. +func (s *ChatSession) Recv() (*pb.ChatResponse, error) { + return s.stream.Recv() +} + +// Close closes the chat session. +func (s *ChatSession) Close() error { + return s.stream.CloseSend() +} diff --git a/go/gitcaddy-ai-client/generate.go b/go/gitcaddy-ai-client/generate.go new file mode 100644 index 0000000..4102505 --- /dev/null +++ b/go/gitcaddy-ai-client/generate.go @@ -0,0 +1,6 @@ +// Copyright 2026 MarketAlly. All rights reserved. +// SPDX-License-Identifier: BSL-1.1 + +package gitcaddyai + +//go:generate protoc --go_out=./proto --go_opt=paths=source_relative --go-grpc_out=./proto --go-grpc_opt=paths=source_relative -I../../protos ../../protos/gitcaddy_ai.proto diff --git a/go/gitcaddy-ai-client/go.mod b/go/gitcaddy-ai-client/go.mod new file mode 100644 index 0000000..2555b0c --- /dev/null +++ b/go/gitcaddy-ai-client/go.mod @@ -0,0 +1,15 @@ +module git.marketally.com/gitcaddy/gitcaddy-ai/go/gitcaddy-ai-client + +go 1.23 + +require ( + google.golang.org/grpc v1.70.0 + google.golang.org/protobuf v1.36.3 +) + +require ( + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect +) diff --git a/src/GitCaddy.AI.Client/GitCaddyAIClient.cs b/src/GitCaddy.AI.Client/GitCaddyAIClient.cs new file mode 100644 index 0000000..ec44d8e --- /dev/null +++ b/src/GitCaddy.AI.Client/GitCaddyAIClient.cs @@ -0,0 +1,190 @@ +// Copyright 2026 MarketAlly. All rights reserved. +// SPDX-License-Identifier: BSL-1.1 + +using GitCaddy.AI.Proto; +using Grpc.Core; +using Grpc.Net.Client; + +namespace GitCaddy.AI.Client; + +/// +/// Client for connecting to GitCaddy AI Service. +/// +public class GitCaddyAIClient : IDisposable +{ + private readonly GrpcChannel _channel; + private readonly GitCaddyAI.GitCaddyAIClient _client; + private bool _disposed; + + public GitCaddyAIClient(string address) + { + _channel = GrpcChannel.ForAddress(address); + _client = new GitCaddyAI.GitCaddyAIClient(_channel); + } + + public GitCaddyAIClient(GrpcChannel channel) + { + _channel = channel; + _client = new GitCaddyAI.GitCaddyAIClient(channel); + } + + /// + /// Reviews a pull request and returns AI-generated feedback. + /// + public async Task ReviewPullRequestAsync( + ReviewPullRequestRequest request, + CancellationToken cancellationToken = default) + { + return await _client.ReviewPullRequestAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Reviews a single commit. + /// + public async Task ReviewCommitAsync( + ReviewCommitRequest request, + CancellationToken cancellationToken = default) + { + return await _client.ReviewCommitAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Summarizes code changes. + /// + public async Task SummarizeChangesAsync( + SummarizeChangesRequest request, + CancellationToken cancellationToken = default) + { + return await _client.SummarizeChangesAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Explains a piece of code. + /// + public async Task ExplainCodeAsync( + ExplainCodeRequest request, + CancellationToken cancellationToken = default) + { + return await _client.ExplainCodeAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Suggests a fix for code with an error. + /// + public async Task SuggestFixAsync( + SuggestFixRequest request, + CancellationToken cancellationToken = default) + { + return await _client.SuggestFixAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Triages an issue with priority and category. + /// + public async Task TriageIssueAsync( + TriageIssueRequest request, + CancellationToken cancellationToken = default) + { + return await _client.TriageIssueAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Suggests labels for an issue. + /// + public async Task SuggestLabelsAsync( + SuggestLabelsRequest request, + CancellationToken cancellationToken = default) + { + return await _client.SuggestLabelsAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Generates a response to an issue. + /// + public async Task GenerateIssueResponseAsync( + GenerateIssueResponseRequest request, + CancellationToken cancellationToken = default) + { + return await _client.GenerateIssueResponseAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Generates documentation for code. + /// + public async Task GenerateDocumentationAsync( + GenerateDocumentationRequest request, + CancellationToken cancellationToken = default) + { + return await _client.GenerateDocumentationAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Generates a commit message from changes. + /// + public async Task GenerateCommitMessageAsync( + GenerateCommitMessageRequest request, + CancellationToken cancellationToken = default) + { + return await _client.GenerateCommitMessageAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Starts a workflow and streams events. + /// + public AsyncServerStreamingCall StartWorkflow( + StartWorkflowRequest request, + CancellationToken cancellationToken = default) + { + return _client.StartWorkflow(request, cancellationToken: cancellationToken); + } + + /// + /// Executes a single task. + /// + public async Task ExecuteTaskAsync( + ExecuteTaskRequest request, + CancellationToken cancellationToken = default) + { + return await _client.ExecuteTaskAsync(request, cancellationToken: cancellationToken); + } + + /// + /// Starts a bidirectional chat session. + /// + public AsyncDuplexStreamingCall Chat( + CancellationToken cancellationToken = default) + { + return _client.Chat(cancellationToken: cancellationToken); + } + + /// + /// Checks service health. + /// + public async Task CheckHealthAsync( + CancellationToken cancellationToken = default) + { + return await _client.CheckHealthAsync(new HealthCheckRequest(), cancellationToken: cancellationToken); + } + + /// + /// Validates a license key. + /// + public async Task ValidateLicenseAsync( + string licenseKey, + CancellationToken cancellationToken = default) + { + return await _client.ValidateLicenseAsync( + new ValidateLicenseRequest { LicenseKey = licenseKey }, + cancellationToken: cancellationToken); + } + + public void Dispose() + { + if (!_disposed) + { + _channel.Dispose(); + _disposed = true; + } + GC.SuppressFinalize(this); + } +} diff --git a/src/GitCaddy.AI.Service/Configuration/AIProviderFactory.cs b/src/GitCaddy.AI.Service/Configuration/AIProviderFactory.cs index acce267..134d876 100644 --- a/src/GitCaddy.AI.Service/Configuration/AIProviderFactory.cs +++ b/src/GitCaddy.AI.Service/Configuration/AIProviderFactory.cs @@ -26,18 +26,18 @@ public class AIProviderFactory : IAIProviderFactory _providerOptions = providerOptions.Value; } - public IAIConversationBuilder CreateConversation() + public AIConversationBuilder CreateConversation() { return CreateConversation(_serviceOptions.DefaultProvider, _serviceOptions.DefaultModel); } - public IAIConversationBuilder CreateConversation(string provider) + public AIConversationBuilder CreateConversation(string provider) { var model = GetDefaultModelForProvider(provider); return CreateConversation(provider, model); } - public IAIConversationBuilder CreateConversation(string provider, string model) + public AIConversationBuilder CreateConversation(string provider, string model) { var aiProvider = ParseProvider(provider); var config = GetProviderConfig(provider); @@ -55,11 +55,6 @@ public class AIProviderFactory : IAIProviderFactory .WithMaxTokens(_serviceOptions.MaxTokens) .WithTemperature(_serviceOptions.Temperature); - if (!string.IsNullOrEmpty(config.BaseUrl)) - { - builder.WithBaseUrl(config.BaseUrl); - } - return builder; } diff --git a/src/GitCaddy.AI.Service/Configuration/IAIProviderFactory.cs b/src/GitCaddy.AI.Service/Configuration/IAIProviderFactory.cs index 5b21657..4aeaff2 100644 --- a/src/GitCaddy.AI.Service/Configuration/IAIProviderFactory.cs +++ b/src/GitCaddy.AI.Service/Configuration/IAIProviderFactory.cs @@ -13,15 +13,15 @@ public interface IAIProviderFactory /// /// Creates a new conversation builder with default settings. /// - IAIConversationBuilder CreateConversation(); + AIConversationBuilder CreateConversation(); /// /// Creates a new conversation builder with a specific provider. /// - IAIConversationBuilder CreateConversation(string provider); + AIConversationBuilder CreateConversation(string provider); /// /// Creates a new conversation builder with a specific provider and model. /// - IAIConversationBuilder CreateConversation(string provider, string model); + AIConversationBuilder CreateConversation(string provider, string model); } diff --git a/src/GitCaddy.AI.Service/GitCaddy.AI.Service.csproj b/src/GitCaddy.AI.Service/GitCaddy.AI.Service.csproj index 384e2fa..7f26101 100644 --- a/src/GitCaddy.AI.Service/GitCaddy.AI.Service.csproj +++ b/src/GitCaddy.AI.Service/GitCaddy.AI.Service.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/GitCaddy.AI.Service/Program.cs b/src/GitCaddy.AI.Service/Program.cs index 7a5f9e4..ff51f58 100644 --- a/src/GitCaddy.AI.Service/Program.cs +++ b/src/GitCaddy.AI.Service/Program.cs @@ -42,14 +42,7 @@ builder.Services.AddGrpc(options => }); // Add health checks -builder.Services.AddGrpcHealthChecks() - .AddCheck("license", () => - { - var validator = builder.Services.BuildServiceProvider().GetRequiredService(); - return validator.IsValid() - ? Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Healthy() - : Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Unhealthy("Invalid license"); - }); +builder.Services.AddHealthChecks(); var app = builder.Build(); @@ -68,7 +61,7 @@ else // Map gRPC services app.MapGrpcService(); -app.MapGrpcHealthChecksService(); +app.MapHealthChecks("/healthz"); // HTTP endpoint for basic health check app.MapGet("/", () => "GitCaddy AI Service is running. Use gRPC to connect."); diff --git a/src/GitCaddy.AI.Service/Properties/launchSettings.json b/src/GitCaddy.AI.Service/Properties/launchSettings.json new file mode 100644 index 0000000..9ed9fb3 --- /dev/null +++ b/src/GitCaddy.AI.Service/Properties/launchSettings.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "GitCaddy.AI.Service": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5050;http://localhost:5051", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": false, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "publishAllPorts": true, + "useSSL": false + } + } +} diff --git a/src/GitCaddy.AI.Service/Services/ChatService.cs b/src/GitCaddy.AI.Service/Services/ChatService.cs index 206c76b..db0498c 100644 --- a/src/GitCaddy.AI.Service/Services/ChatService.cs +++ b/src/GitCaddy.AI.Service/Services/ChatService.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using GitCaddy.AI.Proto; using GitCaddy.AI.Service.Configuration; using Grpc.Core; -using MarketAlly.AIPlugin.Conversation; using Microsoft.Extensions.Options; namespace GitCaddy.AI.Service.Services; @@ -20,7 +19,7 @@ public class ChatService : IChatService private readonly AIServiceOptions _options; // In-memory conversation cache (replace with Redis for production scaling) - private readonly ConcurrentDictionary _conversations = new(); + private readonly ConcurrentDictionary _conversations = new(); public ChatService( ILogger logger, @@ -62,14 +61,17 @@ public class ChatService : IChatService var message = BuildChatMessage(request); // Stream the response - await foreach (var chunk in conversation.SendMessageStreamAsync(message, cancellationToken)) + await foreach (var chunk in conversation.SendStreamingAsync(message, cancellationToken)) { - await responseStream.WriteAsync(new ChatResponse + if (!string.IsNullOrEmpty(chunk.TextDelta)) { - ConversationId = conversationId, - Message = chunk, - IsComplete = false - }, cancellationToken); + await responseStream.WriteAsync(new ChatResponse + { + ConversationId = conversationId, + Message = chunk.TextDelta, + IsComplete = false + }, cancellationToken); + } } // Send completion marker diff --git a/src/GitCaddy.AI.Service/Services/CodeIntelligenceService.cs b/src/GitCaddy.AI.Service/Services/CodeIntelligenceService.cs index 800d9f8..f8c5ec0 100644 --- a/src/GitCaddy.AI.Service/Services/CodeIntelligenceService.cs +++ b/src/GitCaddy.AI.Service/Services/CodeIntelligenceService.cs @@ -40,9 +40,9 @@ public class CodeIntelligenceService : ICodeIntelligenceService .Build(); var prompt = BuildSummarizePrompt(request); - var response = await conversation.SendMessageAsync(prompt, cancellationToken); + var response = await conversation.SendAsync(prompt, cancellationToken); - return ParseSummarizeResponse(response); + return ParseSummarizeResponse(response.FinalMessage ?? ""); } public async Task ExplainCodeAsync( @@ -72,11 +72,11 @@ public class CodeIntelligenceService : ICodeIntelligenceService {(string.IsNullOrEmpty(request.Question) ? "" : $"Specific question: {request.Question}")} """; - var response = await conversation.SendMessageAsync(prompt, cancellationToken); + var response = await conversation.SendAsync(prompt, cancellationToken); return new ExplainCodeResponse { - Explanation = response + Explanation = response.FinalMessage ?? "" }; } @@ -111,11 +111,11 @@ public class CodeIntelligenceService : ICodeIntelligenceService Please suggest a fix. """; - var response = await conversation.SendMessageAsync(prompt, cancellationToken); + var response = await conversation.SendAsync(prompt, cancellationToken); return new SuggestFixResponse { - Explanation = response + Explanation = response.FinalMessage ?? "" }; } diff --git a/src/GitCaddy.AI.Service/Services/CodeReviewService.cs b/src/GitCaddy.AI.Service/Services/CodeReviewService.cs index 44d21f6..39497fa 100644 --- a/src/GitCaddy.AI.Service/Services/CodeReviewService.cs +++ b/src/GitCaddy.AI.Service/Services/CodeReviewService.cs @@ -39,10 +39,10 @@ public class CodeReviewService : ICodeReviewService var prompt = BuildPullRequestReviewPrompt(request); // Get AI response - var response = await conversation.SendMessageAsync(prompt, cancellationToken); + var response = await conversation.SendAsync(prompt, cancellationToken); // Parse and return structured response - return ParsePullRequestReviewResponse(response); + return ParsePullRequestReviewResponse(response.FinalMessage ?? ""); } public async Task ReviewCommitAsync( @@ -53,9 +53,9 @@ public class CodeReviewService : ICodeReviewService .Build(); var prompt = BuildCommitReviewPrompt(request); - var response = await conversation.SendMessageAsync(prompt, cancellationToken); + var response = await conversation.SendAsync(prompt, cancellationToken); - return ParseCommitReviewResponse(response); + return ParseCommitReviewResponse(response.FinalMessage ?? ""); } private static string GetCodeReviewSystemPrompt(ReviewOptions? options) diff --git a/src/GitCaddy.AI.Service/Services/DocumentationService.cs b/src/GitCaddy.AI.Service/Services/DocumentationService.cs index 897de33..d96a2f3 100644 --- a/src/GitCaddy.AI.Service/Services/DocumentationService.cs +++ b/src/GitCaddy.AI.Service/Services/DocumentationService.cs @@ -71,11 +71,11 @@ public class DocumentationService : IDocumentationService ``` """; - var response = await conversation.SendMessageAsync(prompt, cancellationToken); + var response = await conversation.SendAsync(prompt, cancellationToken); return new GenerateDocumentationResponse { - Documentation = response + Documentation = response.FinalMessage ?? "" }; } @@ -128,13 +128,14 @@ public class DocumentationService : IDocumentationService sb.AppendLine(); } - var response = await conversation.SendMessageAsync(sb.ToString(), cancellationToken); + var response = await conversation.SendAsync(sb.ToString(), cancellationToken); + var responseContent = response.FinalMessage ?? ""; // Parse response - first line is primary, rest are alternatives - var lines = response.Split('\n', StringSplitOptions.RemoveEmptyEntries); + var lines = responseContent.Split('\n', StringSplitOptions.RemoveEmptyEntries); var result = new GenerateCommitMessageResponse { - Message = lines.Length > 0 ? lines[0].Trim() : response.Trim() + Message = lines.Length > 0 ? lines[0].Trim() : responseContent.Trim() }; // Extract alternatives if present diff --git a/src/GitCaddy.AI.Service/Services/IssueService.cs b/src/GitCaddy.AI.Service/Services/IssueService.cs index e8c66d2..f28ca0b 100644 --- a/src/GitCaddy.AI.Service/Services/IssueService.cs +++ b/src/GitCaddy.AI.Service/Services/IssueService.cs @@ -32,22 +32,22 @@ public class IssueService : IIssueService var availableLabels = string.Join(", ", request.AvailableLabels); var conversation = _providerFactory.CreateConversation() - .WithSystemPrompt($""" + .WithSystemPrompt($$""" You are an expert issue triager for a software project. Your job is to analyze issues and determine: 1. Priority: critical, high, medium, or low 2. Category: bug, feature, question, docs, enhancement, etc. - 3. Suggested labels from the available labels: {availableLabels} + 3. Suggested labels from the available labels: {{availableLabels}} 4. A brief summary of the issue Respond in JSON format: - {{ + { "priority": "...", "category": "...", "suggested_labels": ["..."], "summary": "..." - }} + } """) .Build(); @@ -61,30 +61,30 @@ public class IssueService : IIssueService Existing labels: {string.Join(", ", request.ExistingLabels)} """; - var response = await conversation.SendMessageAsync(prompt, cancellationToken); + var response = await conversation.SendAsync(prompt, cancellationToken); // Parse JSON response - return ParseTriageResponse(response); + return ParseTriageResponse(response.FinalMessage ?? ""); } public async Task SuggestLabelsAsync( SuggestLabelsRequest request, CancellationToken cancellationToken) { var conversation = _providerFactory.CreateConversation() - .WithSystemPrompt($""" + .WithSystemPrompt($$""" You are an expert at categorizing issues. Given an issue title and body, suggest appropriate labels from the available options. - Available labels: {string.Join(", ", request.AvailableLabels)} + Available labels: {{string.Join(", ", request.AvailableLabels)}} For each suggested label, provide a confidence score (0.0-1.0) and reason. Respond in JSON format: - {{ + { "suggestions": [ - {{"label": "...", "confidence": 0.9, "reason": "..."}} + {"label": "...", "confidence": 0.9, "reason": "..."} ] - }} + } """) .Build(); @@ -95,9 +95,9 @@ public class IssueService : IIssueService {request.Body} """; - var response = await conversation.SendMessageAsync(prompt, cancellationToken); + var response = await conversation.SendAsync(prompt, cancellationToken); - return ParseSuggestLabelsResponse(response, request.AvailableLabels); + return ParseSuggestLabelsResponse(response.FinalMessage ?? "", request.AvailableLabels); } public async Task GenerateResponseAsync( @@ -140,11 +140,11 @@ public class IssueService : IIssueService sb.AppendLine(); sb.AppendLine("Please generate an appropriate response."); - var response = await conversation.SendMessageAsync(sb.ToString(), cancellationToken); + var response = await conversation.SendAsync(sb.ToString(), cancellationToken); return new GenerateIssueResponseResponse { - Response = response + Response = response.FinalMessage ?? "" }; } diff --git a/src/GitCaddy.AI.Service/Services/WorkflowService.cs b/src/GitCaddy.AI.Service/Services/WorkflowService.cs index a5ca73b..2b1667b 100644 --- a/src/GitCaddy.AI.Service/Services/WorkflowService.cs +++ b/src/GitCaddy.AI.Service/Services/WorkflowService.cs @@ -68,9 +68,10 @@ public class WorkflowService : IWorkflowService }, cancellationToken); // Execute the workflow - var response = await conversation.SendMessageAsync( + var aiResponse = await conversation.SendAsync( $"Goal: {request.Goal}\n\nParameters: {string.Join(", ", request.Parameters.Select(p => $"{p.Key}={p.Value}"))}", cancellationToken); + var response = aiResponse.FinalMessage ?? ""; // Send step completed event await responseStream.WriteAsync(new WorkflowEvent @@ -137,12 +138,12 @@ public class WorkflowService : IWorkflowService Please complete this task. """; - var response = await conversation.SendMessageAsync(prompt, cancellationToken); + var aiResponse = await conversation.SendAsync(prompt, cancellationToken); return new ExecuteTaskResponse { Success = true, - Result = response + Result = aiResponse.FinalMessage ?? "" }; } catch (Exception ex)