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)