Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var upstreamModelNotFoundKeywords = []string{"model not found", "unknown model", "not found"}
|
||||
|
||||
func isUpstreamModelNotFoundError(statusCode int, body []byte) bool {
|
||||
if statusCode != http.StatusNotFound {
|
||||
return false
|
||||
}
|
||||
normalized := normalizeModelNotFoundBody(body)
|
||||
if normalized == "" || !strings.Contains(normalized, "model") {
|
||||
return false
|
||||
}
|
||||
return containsModelNotFoundKeyword(normalized)
|
||||
}
|
||||
|
||||
func isModelNotFoundError(statusCode int, body []byte) bool {
|
||||
return isUpstreamModelNotFoundError(statusCode, body) || statusCode == http.StatusNotFound
|
||||
}
|
||||
|
||||
// openAICodexPlanGatedModelPhrase matches the deterministic Codex 400 returned
|
||||
// when a ChatGPT OAuth account's plan cannot serve the requested model, e.g.
|
||||
// {"detail":"The 'gpt-5.6-sol' model is not supported when using Codex with a ChatGPT account."}
|
||||
// The phrase is compared against the normalized body (lowercased, "_"/"-"
|
||||
// folded to spaces), so it also matches the same message embedded in
|
||||
// error.message-style payloads.
|
||||
const openAICodexPlanGatedModelPhrase = "model is not supported when using codex"
|
||||
|
||||
// isOpenAICodexPlanGatedModelError reports whether the upstream response is the
|
||||
// deterministic Codex rejection of a plan-gated model on a ChatGPT account.
|
||||
// Unlike transient failures, retrying the same account cannot succeed until the
|
||||
// account's plan changes, so callers should treat it like model-not-found and
|
||||
// cool the (account, model) pair down instead of re-selecting the account.
|
||||
func isOpenAICodexPlanGatedModelError(statusCode int, body []byte) bool {
|
||||
if statusCode != http.StatusBadRequest {
|
||||
return false
|
||||
}
|
||||
normalized := normalizeModelNotFoundBody(body)
|
||||
if normalized == "" {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(normalized, openAICodexPlanGatedModelPhrase)
|
||||
}
|
||||
|
||||
func containsModelNotFoundKeyword(normalizedBody string) bool {
|
||||
if normalizedBody == "" {
|
||||
return false
|
||||
}
|
||||
for _, keyword := range upstreamModelNotFoundKeywords {
|
||||
if strings.Contains(normalizedBody, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeModelNotFoundBody(body []byte) string {
|
||||
if len(body) == 0 {
|
||||
return ""
|
||||
}
|
||||
normalized := strings.ToLower(string(body))
|
||||
normalized = strings.NewReplacer("_", " ", "-", " ", "\n", " ", "\r", " ", "\t", " ").Replace(normalized)
|
||||
return strings.Join(strings.Fields(normalized), " ")
|
||||
}
|
||||
Reference in New Issue
Block a user