feat: add client libraries, examples, and project documentation
Add Go and .NET client libraries for GitCaddy AI Service with usage examples. Include Business Source License 1.1, Makefile for build automation, and comprehensive README. Update service configuration and all service classes to support new client integration.
This commit is contained in:
101
LICENSE
Normal file
101
LICENSE
Normal file
@@ -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.
|
||||
91
Makefile
Normal file
91
Makefile
Normal file
@@ -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"
|
||||
234
README.md
Normal file
234
README.md
Normal file
@@ -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).
|
||||
14
examples/dotnet/Example.csproj
Normal file
14
examples/dotnet/Example.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\GitCaddy.AI.Client\GitCaddy.AI.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
163
examples/dotnet/Program.cs
Normal file
163
examples/dotnet/Program.cs
Normal file
@@ -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!");
|
||||
7
examples/go/go.mod
Normal file
7
examples/go/go.mod
Normal file
@@ -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
|
||||
147
examples/go/main.go
Normal file
147
examples/go/main.go
Normal file
@@ -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!")
|
||||
}
|
||||
161
go/gitcaddy-ai-client/client.go
Normal file
161
go/gitcaddy-ai-client/client.go
Normal file
@@ -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()
|
||||
}
|
||||
6
go/gitcaddy-ai-client/generate.go
Normal file
6
go/gitcaddy-ai-client/generate.go
Normal file
@@ -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
|
||||
15
go/gitcaddy-ai-client/go.mod
Normal file
15
go/gitcaddy-ai-client/go.mod
Normal file
@@ -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
|
||||
)
|
||||
190
src/GitCaddy.AI.Client/GitCaddyAIClient.cs
Normal file
190
src/GitCaddy.AI.Client/GitCaddyAIClient.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Client for connecting to GitCaddy AI Service.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reviews a pull request and returns AI-generated feedback.
|
||||
/// </summary>
|
||||
public async Task<ReviewPullRequestResponse> ReviewPullRequestAsync(
|
||||
ReviewPullRequestRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.ReviewPullRequestAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reviews a single commit.
|
||||
/// </summary>
|
||||
public async Task<ReviewCommitResponse> ReviewCommitAsync(
|
||||
ReviewCommitRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.ReviewCommitAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summarizes code changes.
|
||||
/// </summary>
|
||||
public async Task<SummarizeChangesResponse> SummarizeChangesAsync(
|
||||
SummarizeChangesRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.SummarizeChangesAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explains a piece of code.
|
||||
/// </summary>
|
||||
public async Task<ExplainCodeResponse> ExplainCodeAsync(
|
||||
ExplainCodeRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.ExplainCodeAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Suggests a fix for code with an error.
|
||||
/// </summary>
|
||||
public async Task<SuggestFixResponse> SuggestFixAsync(
|
||||
SuggestFixRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.SuggestFixAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triages an issue with priority and category.
|
||||
/// </summary>
|
||||
public async Task<TriageIssueResponse> TriageIssueAsync(
|
||||
TriageIssueRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.TriageIssueAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Suggests labels for an issue.
|
||||
/// </summary>
|
||||
public async Task<SuggestLabelsResponse> SuggestLabelsAsync(
|
||||
SuggestLabelsRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.SuggestLabelsAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a response to an issue.
|
||||
/// </summary>
|
||||
public async Task<GenerateIssueResponseResponse> GenerateIssueResponseAsync(
|
||||
GenerateIssueResponseRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.GenerateIssueResponseAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates documentation for code.
|
||||
/// </summary>
|
||||
public async Task<GenerateDocumentationResponse> GenerateDocumentationAsync(
|
||||
GenerateDocumentationRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.GenerateDocumentationAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a commit message from changes.
|
||||
/// </summary>
|
||||
public async Task<GenerateCommitMessageResponse> GenerateCommitMessageAsync(
|
||||
GenerateCommitMessageRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.GenerateCommitMessageAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a workflow and streams events.
|
||||
/// </summary>
|
||||
public AsyncServerStreamingCall<WorkflowEvent> StartWorkflow(
|
||||
StartWorkflowRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _client.StartWorkflow(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a single task.
|
||||
/// </summary>
|
||||
public async Task<ExecuteTaskResponse> ExecuteTaskAsync(
|
||||
ExecuteTaskRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.ExecuteTaskAsync(request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a bidirectional chat session.
|
||||
/// </summary>
|
||||
public AsyncDuplexStreamingCall<ChatRequest, ChatResponse> Chat(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _client.Chat(cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks service health.
|
||||
/// </summary>
|
||||
public async Task<HealthCheckResponse> CheckHealthAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.CheckHealthAsync(new HealthCheckRequest(), cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a license key.
|
||||
/// </summary>
|
||||
public async Task<ValidateLicenseResponse> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,15 +13,15 @@ public interface IAIProviderFactory
|
||||
/// <summary>
|
||||
/// Creates a new conversation builder with default settings.
|
||||
/// </summary>
|
||||
IAIConversationBuilder CreateConversation();
|
||||
AIConversationBuilder CreateConversation();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new conversation builder with a specific provider.
|
||||
/// </summary>
|
||||
IAIConversationBuilder CreateConversation(string provider);
|
||||
AIConversationBuilder CreateConversation(string provider);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new conversation builder with a specific provider and model.
|
||||
/// </summary>
|
||||
IAIConversationBuilder CreateConversation(string provider, string model);
|
||||
AIConversationBuilder CreateConversation(string provider, string model);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.67.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
|
||||
@@ -42,14 +42,7 @@ builder.Services.AddGrpc(options =>
|
||||
});
|
||||
|
||||
// Add health checks
|
||||
builder.Services.AddGrpcHealthChecks()
|
||||
.AddCheck("license", () =>
|
||||
{
|
||||
var validator = builder.Services.BuildServiceProvider().GetRequiredService<ILicenseValidator>();
|
||||
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<GitCaddyAIServiceImpl>();
|
||||
app.MapGrpcHealthChecksService();
|
||||
app.MapHealthChecks("/healthz");
|
||||
|
||||
// HTTP endpoint for basic health check
|
||||
app.MapGet("/", () => "GitCaddy AI Service is running. Use gRPC to connect.");
|
||||
|
||||
21
src/GitCaddy.AI.Service/Properties/launchSettings.json
Normal file
21
src/GitCaddy.AI.Service/Properties/launchSettings.json
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string, IAIConversation> _conversations = new();
|
||||
private readonly ConcurrentDictionary<string, MarketAlly.AIPlugin.Conversation.AIConversation> _conversations = new();
|
||||
|
||||
public ChatService(
|
||||
ILogger<ChatService> 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
|
||||
|
||||
@@ -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<ExplainCodeResponse> 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 ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ReviewCommitResponse> 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<SuggestLabelsResponse> 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<GenerateIssueResponseResponse> 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 ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user