83 lines
2.9 KiB
Go
83 lines
2.9 KiB
Go
package access
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"sub2api-cn-relay-manager/internal/host/sub2api"
|
|
)
|
|
|
|
func (s *Service) verifyGatewayClosure(ctx context.Context, req ClosureRequest, plan closurePlan) (sub2api.GatewayAccessResult, error) {
|
|
if plan.effectiveProbeAPIKey == "" {
|
|
return sub2api.GatewayAccessResult{}, fmt.Errorf("access probe api key is required to verify gateway closure")
|
|
}
|
|
result, err := s.host.CheckGatewayAccess(ctx, sub2api.GatewayAccessCheckRequest{
|
|
APIKey: plan.effectiveProbeAPIKey,
|
|
ExpectedModel: req.ExpectedModel,
|
|
})
|
|
if err != nil {
|
|
return sub2api.GatewayAccessResult{}, fmt.Errorf("check gateway access: %w", err)
|
|
}
|
|
result.EffectiveProbeAPIKey = plan.effectiveProbeAPIKey
|
|
result.EffectiveProbeKeySource = plan.effectiveProbeKeySource
|
|
if result.OK && result.HasExpectedModel && strings.TrimSpace(req.ExpectedModel) != "" {
|
|
completionReq := sub2api.GatewayCompletionCheckRequest{
|
|
APIKey: plan.effectiveProbeAPIKey,
|
|
Model: req.ExpectedModel,
|
|
Prompt: req.Prompt,
|
|
MaxTokens: req.MaxTokens,
|
|
}
|
|
completion, err := s.checkGatewayCompletionWithRetry(ctx, completionReq)
|
|
if err != nil {
|
|
return sub2api.GatewayAccessResult{}, fmt.Errorf("check gateway completion: %w", err)
|
|
}
|
|
completion, err = s.maybeRepairOpenAIResponsesCapability(ctx, req, completionReq, completion)
|
|
if err != nil {
|
|
return sub2api.GatewayAccessResult{}, fmt.Errorf("re-check gateway completion after capability repair: %w", err)
|
|
}
|
|
result.CompletionOK = completion.OK
|
|
result.CompletionStatus = completion.StatusCode
|
|
result.CompletionType = completion.ContentType
|
|
result.CompletionBody = completion.BodyPreview
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Service) checkGatewayCompletionWithRetry(ctx context.Context, req sub2api.GatewayCompletionCheckRequest) (sub2api.GatewayCompletionResult, error) {
|
|
var last sub2api.GatewayCompletionResult
|
|
for attempt := 1; attempt <= gatewayCompletionRetryAttempts; attempt++ {
|
|
completion, err := s.host.CheckGatewayCompletion(ctx, req)
|
|
if err != nil {
|
|
return sub2api.GatewayCompletionResult{}, err
|
|
}
|
|
last = completion
|
|
if completion.OK || !isTransientGatewayCompletionFailure(completion) || attempt == gatewayCompletionRetryAttempts {
|
|
return completion, nil
|
|
}
|
|
timer := time.NewTimer(gatewayCompletionRetryDelay)
|
|
select {
|
|
case <-ctx.Done():
|
|
timer.Stop()
|
|
return last, ctx.Err()
|
|
case <-timer.C:
|
|
}
|
|
}
|
|
return last, nil
|
|
}
|
|
|
|
func isTransientGatewayCompletionFailure(result sub2api.GatewayCompletionResult) bool {
|
|
if result.OK {
|
|
return false
|
|
}
|
|
if result.StatusCode != 0 && result.StatusCode != 429 && result.StatusCode != 502 && result.StatusCode != 503 && result.StatusCode != 504 {
|
|
return false
|
|
}
|
|
body := strings.ToLower(strings.TrimSpace(result.BodyPreview))
|
|
return strings.Contains(body, "service temporarily unavailable") ||
|
|
strings.Contains(body, "no available accounts") ||
|
|
strings.Contains(body, "temporar") ||
|
|
strings.Contains(body, "try again")
|
|
}
|