148 lines
4.2 KiB
Go
148 lines
4.2 KiB
Go
package gateway
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"gpt-load/internal/channel"
|
|
"gpt-load/internal/execution"
|
|
"gpt-load/internal/health"
|
|
)
|
|
|
|
type normalizedCredential struct {
|
|
apiKey string
|
|
payload []byte
|
|
secrets []string
|
|
}
|
|
|
|
func normalizeChannelCredential(
|
|
registry *channel.Registry,
|
|
channelID channel.ID,
|
|
decrypted string,
|
|
) (normalizedCredential, error) {
|
|
if registry == nil || channelID == "" {
|
|
return normalizedCredential{}, fmt.Errorf("credential channel is unavailable")
|
|
}
|
|
trimmed := strings.TrimSpace(decrypted)
|
|
if trimmed == "" {
|
|
return normalizedCredential{}, fmt.Errorf("credential is empty")
|
|
}
|
|
raw := []byte(trimmed)
|
|
validated, err := registry.ValidateCredential(channelID, raw)
|
|
if err != nil {
|
|
return normalizedCredential{}, fmt.Errorf("validate credential: %w", err)
|
|
}
|
|
payload := bytes.Clone(validated.CanonicalJSON())
|
|
apiKey, _ := validated.Value("api_key")
|
|
return normalizedCredential{
|
|
apiKey: apiKey, payload: payload, secrets: credentialSecretValues(payload),
|
|
}, nil
|
|
}
|
|
|
|
func credentialSecretValues(payload []byte) []string {
|
|
var values map[string]string
|
|
if json.Unmarshal(payload, &values) != nil {
|
|
return nil
|
|
}
|
|
fields := []string{
|
|
"api_key", "client_id", "client_secret", "tenant_id", "access_key", "secret_key",
|
|
"session_token", "role_arn", "external_id", "session_name", "service_account_json",
|
|
}
|
|
seen := make(map[string]struct{}, len(fields))
|
|
result := make([]string, 0, len(fields))
|
|
appendValue := func(value string) {
|
|
if value == "" {
|
|
return
|
|
}
|
|
if _, exists := seen[value]; exists {
|
|
return
|
|
}
|
|
seen[value] = struct{}{}
|
|
result = append(result, value)
|
|
}
|
|
for _, field := range fields {
|
|
value := values[field]
|
|
appendValue(value)
|
|
if field != "service_account_json" || value == "" {
|
|
continue
|
|
}
|
|
var nested map[string]json.RawMessage
|
|
if json.Unmarshal([]byte(value), &nested) != nil {
|
|
continue
|
|
}
|
|
for _, nestedField := range []string{"private_key", "private_key_id", "client_email"} {
|
|
var nestedValue string
|
|
if json.Unmarshal(nested[nestedField], &nestedValue) == nil {
|
|
appendValue(nestedValue)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func judgeUpstreamResult(
|
|
result UpstreamResult,
|
|
now time.Time,
|
|
) health.Result {
|
|
result = normalizeUpstreamResultEvidence(result)
|
|
downstreamErr := result.Err
|
|
if result.ExecutionError != nil && !result.Committed {
|
|
downstreamErr = nil
|
|
}
|
|
return health.JudgeExecution(health.ExecutionAttempt{
|
|
DispatchState: result.DispatchState,
|
|
StatusCode: result.StatusCode,
|
|
Header: result.Header,
|
|
Evidence: result.ExecutionError,
|
|
DownstreamCommitted: result.Committed,
|
|
DownstreamErr: downstreamErr,
|
|
Now: now,
|
|
})
|
|
}
|
|
|
|
func normalizeUpstreamResultEvidence(result UpstreamResult) UpstreamResult {
|
|
if result.DispatchState.Valid() {
|
|
return result
|
|
}
|
|
result.DispatchState, result.ExecutionError = inferAttemptEvidence(result)
|
|
result.ResponseStarted = result.ResponseStarted || result.StatusCode != 0
|
|
return result
|
|
}
|
|
|
|
// inferAttemptEvidence keeps the AttemptForwarder boundary fail-safe when a
|
|
// custom implementation supplies HTTP or transport evidence without the
|
|
// normalized execution fields used by the built-in adapter.
|
|
func inferAttemptEvidence(result UpstreamResult) (execution.DispatchState, *execution.ErrorEvidence) {
|
|
dispatchState := execution.DispatchNotSent
|
|
if result.RequestWritten || result.StatusCode != 0 || result.ResponseStarted {
|
|
dispatchState = execution.DispatchMaybeSent
|
|
}
|
|
if result.Err != nil {
|
|
kind := execution.ErrorKindTransport
|
|
switch {
|
|
case errors.Is(result.Err, context.Canceled):
|
|
kind = execution.ErrorKindCanceled
|
|
case errors.Is(result.Err, context.DeadlineExceeded):
|
|
kind = execution.ErrorKindTimeout
|
|
}
|
|
return dispatchState, &execution.ErrorEvidence{Kind: kind, Summary: result.Err.Error()}
|
|
}
|
|
if result.StatusCode >= 200 && result.StatusCode < 300 && !result.ProviderErrorBeforeCommit {
|
|
return dispatchState, nil
|
|
}
|
|
summary := strings.TrimSpace(string(result.ClassificationBody))
|
|
if summary == "" {
|
|
summary = strings.TrimSpace(string(result.Body))
|
|
}
|
|
return dispatchState, &execution.ErrorEvidence{
|
|
Kind: execution.ErrorKindHTTP,
|
|
StatusCode: result.StatusCode,
|
|
Summary: summary,
|
|
}
|
|
}
|