mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-28 00:07:33 +00:00
修复 OpenAI 兼容性 PR 的 CI 回归
This commit is contained in:
@@ -1766,7 +1766,7 @@ func sanitizeOpsSSEDataForPersistence(body []byte) string {
|
||||
out.Grow(len(body))
|
||||
for frameIndex, frame := range frames {
|
||||
if frameIndex > 0 {
|
||||
out.WriteString("\n\n")
|
||||
_, _ = out.WriteString("\n\n")
|
||||
}
|
||||
_, payload := parseOpsSSEFrameEnvelope(frame)
|
||||
trimmedPayload := bytes.TrimSpace(payload)
|
||||
@@ -1779,7 +1779,7 @@ func sanitizeOpsSSEDataForPersistence(body []byte) string {
|
||||
replacement = `{"payload_truncated":true}`
|
||||
}
|
||||
if replacement == "" {
|
||||
out.Write(frame)
|
||||
_, _ = out.Write(frame)
|
||||
continue
|
||||
}
|
||||
wroteData := false
|
||||
@@ -1794,9 +1794,9 @@ func sanitizeOpsSSEDataForPersistence(body []byte) string {
|
||||
wroteData = true
|
||||
}
|
||||
if emittedLine {
|
||||
out.WriteByte('\n')
|
||||
_ = out.WriteByte('\n')
|
||||
}
|
||||
out.Write(line)
|
||||
_, _ = out.Write(line)
|
||||
emittedLine = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ const (
|
||||
openAIOAuth429MaxAccountAttempts = 3
|
||||
openAIStopSchedulingBridgeCooldown = 2 * time.Minute
|
||||
openAIOAuth429StormWindow = 10 * time.Second
|
||||
openAIOAuth429StormThreshold = 20
|
||||
openAIOAuth429StormMaxAccountSwitches = 1
|
||||
)
|
||||
|
||||
@@ -426,17 +425,6 @@ func (s *OpenAIGatewayService) recordOpenAIOAuth429() {
|
||||
s.openaiOAuth429WindowCount.Add(1)
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) isOpenAIOAuth429Storm() bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
windowStart := s.openaiOAuth429WindowStartUnixNano.Load()
|
||||
if windowStart == 0 || time.Since(time.Unix(0, windowStart)) >= openAIOAuth429StormWindow {
|
||||
return false
|
||||
}
|
||||
return s.openaiOAuth429WindowCount.Load() >= openAIOAuth429StormThreshold
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) ShouldStopOpenAIOAuth429Failover(account *Account, statusCode int, failedSwitches int, state *OpenAIOAuth429FailoverState) bool {
|
||||
if failedSwitches < openAIOAuth429StormMaxAccountSwitches {
|
||||
return false
|
||||
|
||||
@@ -30,11 +30,31 @@ func TestAliasOpenAIOAuthReservedToolNames_RewritesDeclarationsAndReferences(t *
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
require.Equal(t, "python", reverse[codexPythonToolAlias])
|
||||
require.Equal(t, codexPythonToolAlias, reqBody["tools"].([]any)[0].(map[string]any)["name"])
|
||||
require.Equal(t, codexPythonToolAlias, reqBody["tool_choice"].(map[string]any)["name"])
|
||||
require.Equal(t, codexPythonToolAlias, reqBody["input"].([]any)[0].(map[string]any)["name"])
|
||||
nested := reqBody["input"].([]any)[1].(map[string]any)["tools"].([]any)[0].(map[string]any)["function"].(map[string]any)
|
||||
require.Equal(t, codexPythonToolAlias, nested["name"])
|
||||
tools, ok := reqBody["tools"].([]any)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, tools)
|
||||
firstTool, ok := tools[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, codexPythonToolAlias, firstTool["name"])
|
||||
toolChoice, ok := reqBody["tool_choice"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, codexPythonToolAlias, toolChoice["name"])
|
||||
input, ok := reqBody["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, input)
|
||||
firstInput, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, codexPythonToolAlias, firstInput["name"])
|
||||
secondInput, ok := input[1].(map[string]any)
|
||||
require.True(t, ok)
|
||||
nestedTools, ok := secondInput["tools"].([]any)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, nestedTools)
|
||||
nestedTool, ok := nestedTools[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
nestedFn, ok := nestedTool["function"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, codexPythonToolAlias, nestedFn["name"])
|
||||
}
|
||||
|
||||
func TestAliasOpenAIOAuthReservedToolNames_CollisionDoesNotMutate(t *testing.T) {
|
||||
@@ -63,7 +83,12 @@ func TestApplyCodexOAuthTransform_ReservedPythonNameIsOAuthOnly(t *testing.T) {
|
||||
result := applyCodexOAuthTransform(reqBody, true, false)
|
||||
require.NoError(t, result.Error)
|
||||
require.Equal(t, "PYTHON", result.ToolNameReverse[codexPythonToolAlias])
|
||||
require.Equal(t, codexPythonToolAlias, reqBody["tools"].([]any)[0].(map[string]any)["name"])
|
||||
tools, ok := reqBody["tools"].([]any)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, tools)
|
||||
tool, ok := tools[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, codexPythonToolAlias, tool["name"])
|
||||
|
||||
apiKeyBody := []byte(`{"type":"response.create","tools":[{"type":"function","name":"python"}]}`)
|
||||
normalized, changed, err := normalizeOpenAIResponsesWebSocketCompatibilityBody(apiKeyBody, &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey})
|
||||
@@ -158,9 +183,21 @@ func TestAliasOpenAIOAuthReservedToolNames_PromptCompatibilityRunsFirst(t *testi
|
||||
require.NoError(t, err)
|
||||
result := applyCodexOAuthTransform(reqBody, true, false)
|
||||
require.NoError(t, result.Error)
|
||||
require.Equal(t, codexPythonToolAlias, reqBody["input"].([]any)[0].(map[string]any)["name"])
|
||||
require.Equal(t, codexPythonToolAlias, reqBody["tools"].([]any)[0].(map[string]any)["name"])
|
||||
require.Equal(t, codexPythonToolAlias, reqBody["tool_choice"].(map[string]any)["name"])
|
||||
input, ok := reqBody["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, input)
|
||||
firstInput, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, codexPythonToolAlias, firstInput["name"])
|
||||
tools, ok := reqBody["tools"].([]any)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, tools)
|
||||
tool, ok := tools[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, codexPythonToolAlias, tool["name"])
|
||||
toolChoice, ok := reqBody["tool_choice"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, codexPythonToolAlias, toolChoice["name"])
|
||||
encoded, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "900719925474099312345", gjson.GetBytes(encoded, "sequence").Raw)
|
||||
|
||||
@@ -115,10 +115,6 @@ func normalizeCodexCallIDForItemType(itemType, id string) string {
|
||||
return compactCodexCallIDForItemType(itemType, candidate)
|
||||
}
|
||||
|
||||
func compactCodexCallID(id string) string {
|
||||
return compactCodexCallIDForItemType("function_call", id)
|
||||
}
|
||||
|
||||
func compactCodexCallIDForItemType(itemType, id string) string {
|
||||
prefix := openAIResponsesToolCallIDPrefix(itemType) + "_"
|
||||
digest := sha256.Sum256([]byte("sub2api:codex-call-id:v1:" + id))
|
||||
|
||||
@@ -38,11 +38,9 @@ func deriveOpenAICyberTranscriptBlockKeys(apiKeyID int64, body []byte) openAICyb
|
||||
if !v.Exists() || (v.Type == gjson.String && strings.TrimSpace(v.String()) == "") {
|
||||
continue
|
||||
}
|
||||
canonical := v.Raw
|
||||
canonical := normalizeCompatSeedJSON(json.RawMessage(v.Raw))
|
||||
if v.Type == gjson.String {
|
||||
canonical = v.String()
|
||||
} else {
|
||||
canonical = normalizeCompatSeedJSON(json.RawMessage(v.Raw))
|
||||
}
|
||||
_, _ = h.Write([]byte("|"))
|
||||
_, _ = h.Write([]byte(field))
|
||||
@@ -63,10 +61,11 @@ func deriveOpenAICyberTranscriptBlockKeys(apiKeyID int64, body []byte) openAICyb
|
||||
hasModelGeneratedItem := false
|
||||
sequence.ForEach(func(_, item gjson.Result) bool {
|
||||
canonical := item.Raw
|
||||
if item.Type == gjson.String {
|
||||
switch item.Type {
|
||||
case gjson.String:
|
||||
encoded, _ := json.Marshal(item.String())
|
||||
canonical = string(encoded)
|
||||
} else if item.Type == gjson.JSON {
|
||||
case gjson.JSON:
|
||||
canonical = normalizeCompatSeedJSON(json.RawMessage(item.Raw))
|
||||
}
|
||||
if strings.TrimSpace(canonical) == "" {
|
||||
|
||||
@@ -371,7 +371,7 @@ func collapseGrokAssistantOutputText(content any) (string, bool) {
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
text.WriteString(value)
|
||||
_, _ = text.WriteString(value)
|
||||
}
|
||||
if strings.TrimSpace(text.String()) == "" {
|
||||
return "", false
|
||||
|
||||
@@ -427,8 +427,6 @@ func (s *OpenAIGatewayService) forwardOpenAIPassthrough(
|
||||
c, account, requestedModel, body, http.StatusBadRequest, signal.message, signal.payload, compactModelFallbackRetried,
|
||||
); retry {
|
||||
body = retryBody
|
||||
upstreamPassthroughModel = fallbackModel
|
||||
compactModelFallbackRetried = true
|
||||
SetOpsUpstreamModel(c, fallbackModel)
|
||||
return s.forwardOpenAIPassthrough(ctx, c, account, body, canonicalImageIntentBody, requestedModel, true, reasoningEffort, reqStream, startTime)
|
||||
}
|
||||
@@ -448,8 +446,6 @@ func (s *OpenAIGatewayService) forwardOpenAIPassthrough(
|
||||
c, account, requestedModel, body, http.StatusBadRequest, signal.message, signal.payload, compactModelFallbackRetried,
|
||||
); retry {
|
||||
body = retryBody
|
||||
upstreamPassthroughModel = fallbackModel
|
||||
compactModelFallbackRetried = true
|
||||
SetOpsUpstreamModel(c, fallbackModel)
|
||||
return s.forwardOpenAIPassthrough(ctx, c, account, body, canonicalImageIntentBody, requestedModel, true, reasoningEffort, reqStream, startTime)
|
||||
}
|
||||
@@ -1513,7 +1509,7 @@ func (s *OpenAIGatewayService) handleOpenAIStreamTerminalAccountSideEffects(
|
||||
}
|
||||
fallthrough
|
||||
case http.StatusUnauthorized, http.StatusTooManyRequests, 529:
|
||||
var ctx context.Context = context.Background()
|
||||
ctx := context.Background()
|
||||
if c != nil && c.Request != nil {
|
||||
ctx = c.Request.Context()
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ func TestOpenAIResponseFlush_BareErrorTimeoutSynthesizesFailed(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reader, writer := io.Pipe()
|
||||
defer writer.Close()
|
||||
defer func() { _ = writer.Close() }()
|
||||
recorder := newOpenAIResponseFlushRecorder()
|
||||
resultCh, errCh := runOpenAIResponseFlushTestAsync(recorder, reader, tt.cfg)
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ func normalizeOpenAIResponsesLegacyIngress(body []byte) ([]byte, bool, error) {
|
||||
request["input"] = legacy.input
|
||||
applyLegacyResponsesTopLevelFields(request, legacy)
|
||||
delete(request, "previous_response_id")
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
// messages is not a Responses field. When native input is present but the
|
||||
|
||||
@@ -311,12 +311,18 @@ func (p *openAIResponsesToolSchemaParser) parseObject(
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
openAIResponsesJSONOneOfKey = []byte(`"oneOf"`)
|
||||
openAIResponsesJSONAnyOfKey = []byte(`"anyOf"`)
|
||||
openAIResponsesJSONEscapeNeedle = []byte{'\\'}
|
||||
)
|
||||
|
||||
func openAIMissingRootObjectUnionTypeEdit(raw []byte, absoluteStart int) (openAIResponsesToolSchemaEdit, bool) {
|
||||
if len(raw) > openAIResponsesObjectUnionMaxSize {
|
||||
return openAIResponsesToolSchemaEdit{}, false
|
||||
}
|
||||
if !bytes.Contains(raw, []byte(`"oneOf"`)) && !bytes.Contains(raw, []byte(`"anyOf"`)) &&
|
||||
!bytes.Contains(raw, []byte{'\\'}) {
|
||||
if !bytes.Contains(raw, openAIResponsesJSONOneOfKey) && !bytes.Contains(raw, openAIResponsesJSONAnyOfKey) &&
|
||||
!bytes.Contains(raw, openAIResponsesJSONEscapeNeedle) {
|
||||
return openAIResponsesToolSchemaEdit{}, false
|
||||
}
|
||||
var schema map[string]json.RawMessage
|
||||
|
||||
@@ -405,7 +405,9 @@ func TestObserveUpstreamMessageBareErrorClearsTurnStateAndFinalizesUsageOnce(t *
|
||||
require.Equal(t, Usage{}, state.turnUsage)
|
||||
require.Nil(t, state.activeTurn)
|
||||
require.Empty(t, state.turnTimingByID)
|
||||
require.True(t, state.consumePendingTurnStartedAt().IsZero())
|
||||
// The error is bound to the active turn id, so settlement must not consume a
|
||||
// later pending start that belongs to the next turn.
|
||||
require.Equal(t, now.Add(-500*time.Millisecond), state.consumePendingTurnStartedAt())
|
||||
|
||||
// Re-observing and settling a later terminal without usage must not re-add the prior turn.
|
||||
observeUpstreamMessage(state, []byte(`{"type":"error","error":{"message":"again"}}`), now, func() time.Time { return now }, nil)
|
||||
|
||||
Reference in New Issue
Block a user