Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1bc4faef4 |
8
.notes/note-1770961030548-nk5207bv5.json
Normal file
8
.notes/note-1770961030548-nk5207bv5.json
Normal file
File diff suppressed because one or more lines are too long
@@ -58,6 +58,27 @@ public class AIProviderFactory : IAIProviderFactory
|
||||
return builder;
|
||||
}
|
||||
|
||||
public AIConversationBuilder CreateConversation(string provider, string model, string apiKey)
|
||||
{
|
||||
var aiProvider = ParseProvider(provider);
|
||||
|
||||
if (string.IsNullOrEmpty(apiKey))
|
||||
{
|
||||
// Fall back to configured key if the per-request key is empty
|
||||
return CreateConversation(provider, model);
|
||||
}
|
||||
|
||||
_logger.LogDebug("Creating conversation with per-request provider override: {Provider}, model {Model}", provider, model);
|
||||
|
||||
var builder = AIConversationBuilder.Create()
|
||||
.UseProvider(aiProvider, apiKey)
|
||||
.UseModel(model)
|
||||
.WithMaxTokens(_serviceOptions.MaxTokens)
|
||||
.WithTemperature(_serviceOptions.Temperature);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static AIProvider ParseProvider(string provider)
|
||||
{
|
||||
return provider.ToLowerInvariant() switch
|
||||
|
||||
@@ -24,4 +24,10 @@ public interface IAIProviderFactory
|
||||
/// Creates a new conversation builder with a specific provider and model.
|
||||
/// </summary>
|
||||
AIConversationBuilder CreateConversation(string provider, string model);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new conversation builder with a specific provider, model, and API key.
|
||||
/// Used for per-request provider overrides from the server's cascade config.
|
||||
/// </summary>
|
||||
AIConversationBuilder CreateConversation(string provider, string model, string apiKey);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// SPDX-License-Identifier: BSL-1.1
|
||||
|
||||
using GitCaddy.AI.Service.Services;
|
||||
using GitCaddy.AI.Service.Configuration;
|
||||
using GitCaddy.AI.Service.Licensing;
|
||||
using MarketAlly.AIPlugin.Conversation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace GitCaddy.AI.Service.Controllers;
|
||||
@@ -19,6 +21,7 @@ public class AIController : ControllerBase
|
||||
private readonly ICodeIntelligenceService _codeIntelligenceService;
|
||||
private readonly IIssueService _issueService;
|
||||
private readonly IDocumentationService _documentationService;
|
||||
private readonly IAIProviderFactory _providerFactory;
|
||||
private readonly ILicenseValidator _licenseValidator;
|
||||
private readonly ILogger<AIController> _logger;
|
||||
|
||||
@@ -27,6 +30,7 @@ public class AIController : ControllerBase
|
||||
ICodeIntelligenceService codeIntelligenceService,
|
||||
IIssueService issueService,
|
||||
IDocumentationService documentationService,
|
||||
IAIProviderFactory providerFactory,
|
||||
ILicenseValidator licenseValidator,
|
||||
ILogger<AIController> logger)
|
||||
{
|
||||
@@ -34,10 +38,26 @@ public class AIController : ControllerBase
|
||||
_codeIntelligenceService = codeIntelligenceService;
|
||||
_issueService = issueService;
|
||||
_documentationService = documentationService;
|
||||
_providerFactory = providerFactory;
|
||||
_licenseValidator = licenseValidator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a conversation builder using per-request provider config if available,
|
||||
/// otherwise falls back to the sidecar's default configuration.
|
||||
/// </summary>
|
||||
private AIConversationBuilder CreateConversation(ProviderConfigDto? providerConfig)
|
||||
{
|
||||
if (providerConfig is not null &&
|
||||
!string.IsNullOrEmpty(providerConfig.Provider))
|
||||
{
|
||||
var model = !string.IsNullOrEmpty(providerConfig.Model) ? providerConfig.Model : providerConfig.Provider;
|
||||
return _providerFactory.CreateConversation(providerConfig.Provider, model, providerConfig.ApiKey ?? "");
|
||||
}
|
||||
return _providerFactory.CreateConversation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Health check endpoint
|
||||
/// </summary>
|
||||
@@ -388,6 +408,88 @@ public class AIController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspect a workflow YAML file for issues
|
||||
/// </summary>
|
||||
[HttpPost("workflows/inspect")]
|
||||
public async Task<IActionResult> InspectWorkflow([FromBody] InspectWorkflowDto request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var conversation = CreateConversation(request.ProviderConfig)
|
||||
.WithSystemPrompt("""
|
||||
You are a CI/CD workflow expert. Analyze the provided GitHub Actions / Gitea Actions
|
||||
workflow YAML file for issues including:
|
||||
- Syntax errors and invalid YAML
|
||||
- Missing required fields (runs-on, steps, etc.)
|
||||
- Security issues (hardcoded secrets, overly broad permissions, script injection)
|
||||
- Performance issues (unnecessary steps, missing caching)
|
||||
- Compatibility issues with the available runner labels
|
||||
- Best practice violations
|
||||
|
||||
Respond with a JSON object containing:
|
||||
{
|
||||
"valid": true/false,
|
||||
"issues": [{"line": 0, "severity": "error|warning|info", "message": "...", "fix": "..."}],
|
||||
"suggestions": ["..."]
|
||||
}
|
||||
Only respond with the JSON object, no other text.
|
||||
""")
|
||||
.Build();
|
||||
|
||||
var prompt = $"Workflow file: {request.FilePath}\n\n```yaml\n{request.Content}\n```";
|
||||
if (request.RunnerLabels is { Count: > 0 })
|
||||
{
|
||||
prompt += $"\n\nAvailable runner labels: {string.Join(", ", request.RunnerLabels)}";
|
||||
}
|
||||
|
||||
var aiResponse = await conversation.SendAsync(prompt, cancellationToken);
|
||||
var responseText = aiResponse.FinalMessage ?? "{}";
|
||||
|
||||
// Parse the AI response as JSON
|
||||
try
|
||||
{
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<InspectWorkflowResult>(
|
||||
responseText,
|
||||
new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
valid = result?.Valid ?? true,
|
||||
issues = result?.Issues?.Select(i => new
|
||||
{
|
||||
line = i.Line,
|
||||
severity = i.Severity,
|
||||
message = i.Message,
|
||||
fix = i.Fix
|
||||
}).ToList() ?? [],
|
||||
suggestions = result?.Suggestions ?? [],
|
||||
confidence = 0.8,
|
||||
input_tokens = 0,
|
||||
output_tokens = 0
|
||||
});
|
||||
}
|
||||
catch (System.Text.Json.JsonException)
|
||||
{
|
||||
// If AI didn't return valid JSON, wrap the text response
|
||||
return Ok(new
|
||||
{
|
||||
valid = true,
|
||||
issues = Array.Empty<object>(),
|
||||
suggestions = new[] { responseText },
|
||||
confidence = 0.5,
|
||||
input_tokens = 0,
|
||||
output_tokens = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to inspect workflow");
|
||||
return StatusCode(500, new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static object MapReviewResponse(Proto.ReviewPullRequestResponse response)
|
||||
{
|
||||
return new
|
||||
@@ -425,8 +527,23 @@ public class AIController : ControllerBase
|
||||
}
|
||||
|
||||
// DTO classes for REST API
|
||||
|
||||
/// <summary>
|
||||
/// Per-request provider configuration override.
|
||||
/// When provided, overrides the sidecar's default provider/model/key for this request.
|
||||
/// </summary>
|
||||
public class ProviderConfigDto
|
||||
{
|
||||
public string? Provider { get; set; }
|
||||
public string? Model { get; set; }
|
||||
[System.Text.Json.Serialization.JsonPropertyName("api_key")]
|
||||
public string? ApiKey { get; set; }
|
||||
}
|
||||
|
||||
public class ReviewPullRequestDto
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonPropertyName("provider_config")]
|
||||
public ProviderConfigDto? ProviderConfig { get; set; }
|
||||
public long RepoId { get; set; }
|
||||
public long PullRequestId { get; set; }
|
||||
public string? BaseBranch { get; set; }
|
||||
@@ -460,6 +577,8 @@ public class ReviewOptionsDto
|
||||
|
||||
public class TriageIssueDto
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonPropertyName("provider_config")]
|
||||
public ProviderConfigDto? ProviderConfig { get; set; }
|
||||
public long RepoId { get; set; }
|
||||
public long IssueId { get; set; }
|
||||
public string? Title { get; set; }
|
||||
@@ -470,6 +589,8 @@ public class TriageIssueDto
|
||||
|
||||
public class SuggestLabelsDto
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonPropertyName("provider_config")]
|
||||
public ProviderConfigDto? ProviderConfig { get; set; }
|
||||
public long RepoId { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Body { get; set; }
|
||||
@@ -478,6 +599,8 @@ public class SuggestLabelsDto
|
||||
|
||||
public class ExplainCodeDto
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonPropertyName("provider_config")]
|
||||
public ProviderConfigDto? ProviderConfig { get; set; }
|
||||
public long RepoId { get; set; }
|
||||
public string? FilePath { get; set; }
|
||||
public string? Code { get; set; }
|
||||
@@ -488,6 +611,8 @@ public class ExplainCodeDto
|
||||
|
||||
public class SummarizeChangesDto
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonPropertyName("provider_config")]
|
||||
public ProviderConfigDto? ProviderConfig { get; set; }
|
||||
public long RepoId { get; set; }
|
||||
public List<FileDiffDto>? Files { get; set; }
|
||||
public string? Context { get; set; }
|
||||
@@ -495,6 +620,8 @@ public class SummarizeChangesDto
|
||||
|
||||
public class GenerateDocumentationDto
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonPropertyName("provider_config")]
|
||||
public ProviderConfigDto? ProviderConfig { get; set; }
|
||||
public long RepoId { get; set; }
|
||||
public string? FilePath { get; set; }
|
||||
public string? Code { get; set; }
|
||||
@@ -505,6 +632,8 @@ public class GenerateDocumentationDto
|
||||
|
||||
public class GenerateCommitMessageDto
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonPropertyName("provider_config")]
|
||||
public ProviderConfigDto? ProviderConfig { get; set; }
|
||||
public long RepoId { get; set; }
|
||||
public List<FileDiffDto>? Files { get; set; }
|
||||
public string? Style { get; set; }
|
||||
@@ -512,6 +641,8 @@ public class GenerateCommitMessageDto
|
||||
|
||||
public class GenerateIssueResponseDto
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonPropertyName("provider_config")]
|
||||
public ProviderConfigDto? ProviderConfig { get; set; }
|
||||
public long RepoId { get; set; }
|
||||
public long IssueId { get; set; }
|
||||
public string? Title { get; set; }
|
||||
@@ -527,3 +658,32 @@ public class IssueCommentDto
|
||||
public string? Body { get; set; }
|
||||
public string? CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class InspectWorkflowDto
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonPropertyName("provider_config")]
|
||||
public ProviderConfigDto? ProviderConfig { get; set; }
|
||||
[System.Text.Json.Serialization.JsonPropertyName("repo_id")]
|
||||
public long RepoId { get; set; }
|
||||
[System.Text.Json.Serialization.JsonPropertyName("file_path")]
|
||||
public string? FilePath { get; set; }
|
||||
public string? Content { get; set; }
|
||||
[System.Text.Json.Serialization.JsonPropertyName("runner_labels")]
|
||||
public List<string>? RunnerLabels { get; set; }
|
||||
}
|
||||
|
||||
// Internal model for parsing AI response
|
||||
internal class InspectWorkflowResult
|
||||
{
|
||||
public bool Valid { get; set; }
|
||||
public List<InspectWorkflowIssue>? Issues { get; set; }
|
||||
public List<string>? Suggestions { get; set; }
|
||||
}
|
||||
|
||||
internal class InspectWorkflowIssue
|
||||
{
|
||||
public int Line { get; set; }
|
||||
public string? Severity { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public string? Fix { get; set; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user