Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions backend/internal/service/gateway_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2675,6 +2675,19 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
return nil, fmt.Errorf("parse request: empty request")
}

// 防止非 Anthropic/Antigravity 平台的账号被错误地通过 Claude 端点转发。
// 例如 OpenAI 账号的 token 发到 Anthropic API 会导致 401 并触发 failover 禁用账号。
if account.Platform != PlatformAnthropic && account.Platform != PlatformAntigravity {
c.JSON(http.StatusBadRequest, gin.H{
"type": "error",
"error": gin.H{
"type": "invalid_request_error",
"message": fmt.Sprintf("Account platform %q is not compatible with /v1/messages endpoint", account.Platform),
},
})
return nil, fmt.Errorf("platform mismatch: account %d platform=%s not compatible with claude endpoint", account.ID, account.Platform)
}

body := parsed.Body
reqModel := parsed.Model
reqStream := parsed.Stream
Expand Down
9 changes: 8 additions & 1 deletion backend/internal/service/openai_codex_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,15 @@ func applyCodexOAuthTransform(reqBody map[string]any, isCodexCLI bool) codexTran
}

// 续链场景保留 item_reference 与 id,避免 call_id 上下文丢失。
// 但 store=false 时上游不会持久化 item,引用必然失败,
// 因此即使续链也必须移除 item_reference 并清理 id 字段。
storeFalse := false
if v, ok := reqBody["store"].(bool); ok && !v {
storeFalse = true
}
if input, ok := reqBody["input"].([]any); ok {
input = filterCodexInput(input, needsToolContinuation)
preserveRefs := needsToolContinuation && !storeFalse
input = filterCodexInput(input, preserveRefs)
reqBody["input"] = input
result.Modified = true
}
Expand Down
21 changes: 10 additions & 11 deletions backend/internal/service/openai_codex_transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import (
)

func TestApplyCodexOAuthTransform_ToolContinuationPreservesInput(t *testing.T) {
// 续链场景:保留 item_reference 与 id,但不再强制 store=true。
// 续链场景 + store=false:item_reference 被移除(上游不持久化,引用必然 404),
// function_call_output 保留但 id 被清理,call_id 保留用于工具调用关联。
setupCodexCache(t)

reqBody := map[string]any{
Expand All @@ -25,25 +26,23 @@ func TestApplyCodexOAuthTransform_ToolContinuationPreservesInput(t *testing.T) {

applyCodexOAuthTransform(reqBody, false)

// 未显式设置 store=true,默认为 false。
// store 强制为 false(OAuth 账号)
store, ok := reqBody["store"].(bool)
require.True(t, ok)
require.False(t, store)

input, ok := reqBody["input"].([]any)
require.True(t, ok)
require.Len(t, input, 2)
// item_reference 被移除,只剩 function_call_output。
require.Len(t, input, 1)

// 校验 input[0] 为 map,避免断言失败导致测试中断。
first, ok := input[0].(map[string]any)
require.True(t, ok)
require.Equal(t, "item_reference", first["type"])
require.Equal(t, "ref1", first["id"])

// 校验 input[1] 为 map,确保后续字段断言安全。
second, ok := input[1].(map[string]any)
require.True(t, ok)
require.Equal(t, "o1", second["id"])
require.Equal(t, "function_call_output", first["type"])
require.Equal(t, "call_1", first["call_id"])
// id 被清理(store=false 下无意义)。
_, hasID := first["id"]
require.False(t, hasID)
}

func TestApplyCodexOAuthTransform_ExplicitStoreFalsePreserved(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions backend/internal/service/openai_gateway_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,16 @@ func (s *OpenAIGatewayService) handleErrorResponse(ctx context.Context, resp *ht
var statusCode int

switch resp.StatusCode {
case 400, 404, 409, 422:
// Client errors: pass through upstream status and message so the caller
// can act on the real problem (e.g. referencing a non-persisted response ID).
statusCode = resp.StatusCode
errType = "invalid_request_error"
if upstreamMsg != "" {
errMsg = upstreamMsg
} else {
errMsg = "Upstream request rejected"
}
case 401:
statusCode = http.StatusBadGateway
errType = "upstream_error"
Expand Down