Files
sub2api/backend/internal/service/openai_responses_namespace.go
T
shaw fa1641f05f 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.
2026-07-14 14:48:22 +08:00

84 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"bytes"
"encoding/json"
"fmt"
"github.com/Wei-Shaw/sub2api/internal/pkg/apicompat"
"github.com/gin-gonic/gin"
)
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
}
var requestBody map[string]any
if err := json.Unmarshal(body, &requestBody); err != nil {
return body, fmt.Errorf("decode OpenAI namespace body: %w", err)
}
names, changed, err := apicompat.FlattenResponsesNamespacesExcept(requestBody, map[string]bool{"image_gen": true})
if err != nil {
return body, err
}
if !changed {
return body, nil
}
rebuilt, err := marshalOpenAIUpstreamJSON(requestBody)
if err != nil {
return body, fmt.Errorf("encode OpenAI namespace body: %w", err)
}
setOpenAIResponsesNamespaceNames(c, names)
return rebuilt, nil
}
func setOpenAIResponsesNamespaceNames(c *gin.Context, names map[string]apicompat.ResponsesNamespaceName) {
if c != nil && len(names) > 0 {
c.Set(openAIResponsesNamespaceNamesContextKey, names)
}
}
func openAIResponsesNamespaceNames(c *gin.Context) map[string]apicompat.ResponsesNamespaceName {
if c == nil {
return nil
}
value, ok := c.Get(openAIResponsesNamespaceNamesContextKey)
if !ok {
return nil
}
names, _ := value.(map[string]apicompat.ResponsesNamespaceName)
return names
}
func restoreOpenAIResponsesNamespacePayload(c *gin.Context, payload []byte) ([]byte, error) {
names := openAIResponsesNamespaceNames(c)
if len(names) == 0 || !json.Valid(payload) {
return payload, nil
}
restored, changed, err := apicompat.RestoreResponsesNamespaceCalls(payload, names)
if err != nil {
return payload, err
}
if changed {
return restored, nil
}
return payload, nil
}