first commit

This commit is contained in:
npc0-hue
2026-07-11 14:56:10 +08:00
commit 7e05d0a4e7
660 changed files with 78119 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
package service
import (
"errors"
"strings"
"browser.local/platform/domain"
"browser.local/platform/repo"
"browser.local/platform/validator"
)
type AIProviderClient interface {
Invoke(provider domain.AIProvider, request domain.AIInvocationRequest) (domain.AIProviderInvocationResult, error)
}
type MockAIProviderClient struct{}
func (MockAIProviderClient) Invoke(provider domain.AIProvider, request domain.AIInvocationRequest) (domain.AIProviderInvocationResult, error) {
model := request.Model
if model == "" {
model = provider.DefaultModel
}
if model == "" && len(provider.Models) > 0 {
model = provider.Models[0]
}
recommendation := "Mock AI recommendation for " + request.Purpose + ": review the proposed change before dispatch."
result := domain.AIProviderInvocationResult{
Recommendation: recommendation,
Usage: domain.AIInvocationUsage{
ProviderID: provider.ID,
Model: model,
InputTokens: boundedTokenEstimate(request.Prompt + request.CurrentConfig),
OutputTokens: boundedTokenEstimate(recommendation),
Mocked: true,
},
}
if request.Purpose == "config.suggest" || request.Purpose == "config.generate" {
result.SuggestedConfig = buildSuggestedConfig(request.CurrentConfig, request.Prompt)
}
return result, nil
}
func (svc *CoreService) InvokeAIForSession(sessionID string, request domain.AIInvocationRequest) (domain.AIInvocationResponse, error) {
request = domain.CopyAIInvocationRequest(request)
if err := validator.ValidateAIInvocationRequest(request); err != nil {
return domain.AIInvocationResponse{}, err
}
if _, err := svc.GetCurrentUser(sessionID); err != nil {
return domain.AIInvocationResponse{}, err
}
if request.ServerInstanceID != "" {
instance, err := svc.GetServerInstanceForSession(sessionID, request.ServerInstanceID)
if err != nil {
return domain.AIInvocationResponse{}, err
}
if request.PluginID != "" && instance.PluginID != request.PluginID {
return safeAIDenial(request, "plugin scope does not match server instance"), nil
}
}
if request.PluginID != "" {
plugin, err := svc.store.GamePlugins().Get(request.PluginID)
if err != nil {
return domain.AIInvocationResponse{}, err
}
authorization, err := validator.AuthorizePluginBridgeAction(plugin, domain.PluginBridgeAuthorizeRequest{
PluginID: request.PluginID,
RouteKey: request.RouteKey,
ServerInstanceID: request.ServerInstanceID,
Action: domain.PluginBridgeActionAIInvoke,
AIPurpose: request.Purpose,
})
if err != nil {
return domain.AIInvocationResponse{}, err
}
if !authorization.Allowed {
return safeAIDenial(request, authorization.Reason), nil
}
}
provider, err := svc.selectAIProvider(request.ProviderID)
if err != nil {
return domain.AIInvocationResponse{}, err
}
result, err := svc.aiProviderClient.Invoke(provider, request)
if err != nil {
return domain.CopyAIInvocationResponse(domain.AIInvocationResponse{
RequestID: request.RequestID,
Purpose: request.Purpose,
ProviderID: provider.ID,
Model: safeModel(request.Model, provider),
Status: "error",
Usage: domain.AIInvocationUsage{ProviderID: provider.ID, Model: safeModel(request.Model, provider), Mocked: true},
Error: &domain.AIInvocationSafeError{Code: "provider_failed", Message: safeBridgeReason(err.Error())},
}), nil
}
response := domain.AIInvocationResponse{
RequestID: request.RequestID,
Purpose: request.Purpose,
ProviderID: provider.ID,
Model: result.Usage.Model,
Status: "ok",
Recommendation: result.Recommendation,
Usage: result.Usage,
}
if result.SuggestedConfig != "" {
response.ConfigRecommendation = &domain.AIConfigRecommendation{Key: "server.properties", SuggestedConfig: result.SuggestedConfig, DiffSummary: "review required before config write dispatch"}
}
if err := validator.ValidateAIInvocationResponse(response); err != nil {
return domain.AIInvocationResponse{}, err
}
return domain.CopyAIInvocationResponse(response), nil
}
func (svc *CoreService) selectAIProvider(id string) (domain.AIProvider, error) {
if id != "" {
provider, err := svc.store.AIProviders().Get(id)
if err != nil {
return domain.AIProvider{}, err
}
if provider.Status != domain.AIProviderStatusActive {
return domain.AIProvider{}, ErrForbidden
}
return provider, nil
}
providers, err := svc.store.AIProviders().List(domain.AIProviderFilter{Status: domain.AIProviderStatusActive})
if err != nil {
return domain.AIProvider{}, err
}
if len(providers) == 0 {
return domain.AIProvider{}, repo.ErrNotFound
}
return providers[0], nil
}
func safeAIDenial(request domain.AIInvocationRequest, reason string) domain.AIInvocationResponse {
return domain.CopyAIInvocationResponse(domain.AIInvocationResponse{
RequestID: request.RequestID,
Purpose: request.Purpose,
Status: "denied",
Error: &domain.AIInvocationSafeError{Code: "permission_denied", Message: safeBridgeReason(reason)},
})
}
func safeModel(model string, provider domain.AIProvider) string {
if strings.TrimSpace(model) != "" {
return model
}
if provider.DefaultModel != "" {
return provider.DefaultModel
}
if len(provider.Models) > 0 {
return provider.Models[0]
}
return "mock-model"
}
func buildSuggestedConfig(currentConfig string, prompt string) string {
base := strings.TrimRight(currentConfig, "\n")
if base == "" {
base = "# generated server config"
}
if strings.Contains(strings.ToLower(prompt), "pvp") && !strings.Contains(base, "pvp=") {
base += "\npvp=false"
}
return base + "\n# ai.recommendation=review-required\n"
}
func boundedTokenEstimate(value string) int {
count := len([]rune(value)) / 4
if count < 1 {
return 1
}
if count > 4096 {
return 4096
}
return count
}
type failingAIProviderClient struct{ err error }
func (client failingAIProviderClient) Invoke(domain.AIProvider, domain.AIInvocationRequest) (domain.AIProviderInvocationResult, error) {
if client.err == nil {
return domain.AIProviderInvocationResult{}, errors.New("provider failed")
}
return domain.AIProviderInvocationResult{}, client.err
}