mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-21 14:19:18 +08:00
fix: keep namespace verbatim on WSv2 forwards and check test type assertions
WSv2 egress relays upstream events verbatim without the HTTP-path namespace restore, so flattening requests that take the WSv2 branch would surface flattened tool names the client cannot match. Resolve the WS transport decision before flattening and skip flattening only when the request will actually go WSv2 (passthrough accounts return via HTTP before the WSv2 branch and still flatten). Also check all type assertions in responses_namespace_test.go to satisfy golangci-lint errcheck.
This commit is contained in:
@@ -31,20 +31,31 @@ func TestFlattenResponsesNamespaces_RewritesDeclarationHistoryAndChoice(t *testi
|
||||
require.True(t, changed)
|
||||
require.Equal(t, ResponsesNamespaceName{Namespace: "collaboration", Name: "spawn_agent"}, names["collaboration__spawn_agent"])
|
||||
|
||||
tools := req["tools"].([]any)
|
||||
tools, ok := req["tools"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, tools, 2)
|
||||
require.Equal(t, "plain", tools[0].(map[string]any)["name"])
|
||||
require.Equal(t, "collaboration__spawn_agent", tools[1].(map[string]any)["name"])
|
||||
require.Equal(t, "spawn", tools[1].(map[string]any)["description"])
|
||||
plainTool, ok := tools[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "plain", plainTool["name"])
|
||||
flatTool, ok := tools[1].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "collaboration__spawn_agent", flatTool["name"])
|
||||
require.Equal(t, "spawn", flatTool["description"])
|
||||
|
||||
choice := req["tool_choice"].(map[string]any)
|
||||
choice, ok := req["tool_choice"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "collaboration__spawn_agent", choice["name"])
|
||||
require.NotContains(t, choice, "namespace")
|
||||
|
||||
call := req["input"].([]any)[0].(map[string]any)
|
||||
input, ok := req["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, input, 2)
|
||||
call, ok := input[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "collaboration__spawn_agent", call["name"])
|
||||
require.NotContains(t, call, "namespace")
|
||||
message := req["input"].([]any)[1].(map[string]any)
|
||||
message, ok := input[1].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "spawn_agent", message["name"])
|
||||
require.Equal(t, "collaboration", message["namespace"])
|
||||
require.Equal(t, "gpt-5.5", req["model"])
|
||||
@@ -96,11 +107,17 @@ func TestFlattenResponsesNamespacesExcept_PreservesBuiltInNamespaceAndChoice(t *
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
require.Contains(t, names, "collaboration__spawn_agent")
|
||||
tools := req["tools"].([]any)
|
||||
require.Equal(t, "namespace", tools[0].(map[string]any)["type"])
|
||||
require.Equal(t, "image_gen", tools[0].(map[string]any)["name"])
|
||||
require.Equal(t, "function", tools[1].(map[string]any)["type"])
|
||||
require.Equal(t, "collaboration__spawn_agent", tools[1].(map[string]any)["name"])
|
||||
tools, ok := req["tools"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, tools, 2)
|
||||
preservedTool, ok := tools[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "namespace", preservedTool["type"])
|
||||
require.Equal(t, "image_gen", preservedTool["name"])
|
||||
flatTool, ok := tools[1].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "function", flatTool["type"])
|
||||
require.Equal(t, "collaboration__spawn_agent", flatTool["name"])
|
||||
require.Equal(t, map[string]any{"type": "namespace", "name": "image_gen"}, req["tool_choice"])
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,11 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
|
||||
if normalized {
|
||||
body = normalizedBody
|
||||
}
|
||||
if account.Type == AccountTypeOAuth {
|
||||
wsDecision := s.getOpenAIWSProtocolResolver().Resolve(account)
|
||||
// 仅允许 WS 入站请求走 WS 上游,避免出现 HTTP -> WS 协议混用。
|
||||
wsDecision = resolveOpenAIWSDecisionByClientTransport(wsDecision, GetOpenAIClientTransport(c))
|
||||
passthroughEnabled := account.IsOpenAIPassthroughEnabled()
|
||||
if shouldFlattenOpenAIResponsesNamespaces(account, wsDecision.Transport, passthroughEnabled) {
|
||||
body, err = flattenOpenAIResponsesNamespaces(c, body)
|
||||
if err != nil {
|
||||
setOpsUpstreamError(c, http.StatusBadRequest, err.Error(), "")
|
||||
@@ -74,10 +78,6 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
|
||||
if isCodexCLI {
|
||||
codexImageGenerationExplicitToolPolicy = account.CodexImageGenerationExplicitToolPolicy()
|
||||
}
|
||||
wsDecision := s.getOpenAIWSProtocolResolver().Resolve(account)
|
||||
clientTransport := GetOpenAIClientTransport(c)
|
||||
// 仅允许 WS 入站请求走 WS 上游,避免出现 HTTP -> WS 协议混用。
|
||||
wsDecision = resolveOpenAIWSDecisionByClientTransport(wsDecision, clientTransport)
|
||||
if c != nil {
|
||||
c.Set("openai_ws_transport_decision", string(wsDecision.Transport))
|
||||
c.Set("openai_ws_transport_reason", wsDecision.Reason)
|
||||
@@ -106,7 +106,6 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
|
||||
}
|
||||
return nil, errors.New("openai ws v1 is temporarily unsupported; use ws v2")
|
||||
}
|
||||
passthroughEnabled := account.IsOpenAIPassthroughEnabled()
|
||||
if passthroughEnabled {
|
||||
if isCodexCLI && codexImageGenerationExplicitToolPolicy == codexImageGenerationExplicitToolPolicyStrip {
|
||||
strippedBody, changed, stripErr := stripOpenAIImageGenerationToolsFromRawPayload(body)
|
||||
|
||||
@@ -11,6 +11,21 @@ import (
|
||||
|
||||
const openAIResponsesNamespaceNamesContextKey = "openai_responses_namespace_names"
|
||||
|
||||
// shouldFlattenOpenAIResponsesNamespaces 判定原生 Responses 转发前是否摊平
|
||||
// Codex namespace 工具。WSv2 上游原生支持 namespace,且 WS 出口
|
||||
// (openai_ws_forwarder_v2)原样转发上游事件、不经 HTTP 回程还原,摊平后的
|
||||
// 平名无法还原会破坏客户端工具匹配,因此实际走 WSv2 分支的请求保持 namespace
|
||||
// 原样。透传账号先于 WSv2 分支经 HTTP 转发返回,仍需摊平。
|
||||
func shouldFlattenOpenAIResponsesNamespaces(account *Account, transport OpenAIUpstreamTransport, passthroughEnabled bool) bool {
|
||||
if account == nil || account.Type != AccountTypeOAuth {
|
||||
return false
|
||||
}
|
||||
if transport == OpenAIUpstreamTransportResponsesWebsocketV2 && !passthroughEnabled {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func flattenOpenAIResponsesNamespaces(c *gin.Context, body []byte) ([]byte, error) {
|
||||
if !bytes.Contains(body, []byte(`"namespace"`)) {
|
||||
return body, nil
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestShouldFlattenOpenAIResponsesNamespaces(t *testing.T) {
|
||||
oauth := &Account{Type: AccountTypeOAuth}
|
||||
apiKey := &Account{Type: AccountTypeAPIKey}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
account *Account
|
||||
transport OpenAIUpstreamTransport
|
||||
passthroughEnabled bool
|
||||
want bool
|
||||
}{
|
||||
{name: "oauth_http", account: oauth, transport: OpenAIUpstreamTransportHTTPSSE, want: true},
|
||||
{name: "oauth_http_passthrough", account: oauth, transport: OpenAIUpstreamTransportHTTPSSE, passthroughEnabled: true, want: true},
|
||||
// WSv2 出口原样转发上游事件、不做回程还原,摊平会让客户端收到无法匹配的平名。
|
||||
{name: "oauth_wsv2", account: oauth, transport: OpenAIUpstreamTransportResponsesWebsocketV2, want: false},
|
||||
// 透传账号先于 WSv2 分支经 HTTP 转发返回,仍需摊平。
|
||||
{name: "oauth_wsv2_passthrough", account: oauth, transport: OpenAIUpstreamTransportResponsesWebsocketV2, passthroughEnabled: true, want: true},
|
||||
{name: "apikey_http", account: apiKey, transport: OpenAIUpstreamTransportHTTPSSE, want: false},
|
||||
{name: "nil_account", account: nil, transport: OpenAIUpstreamTransportHTTPSSE, want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, shouldFlattenOpenAIResponsesNamespaces(tt.account, tt.transport, tt.passthroughEnabled))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user