Merge pull request #7426 from xlplbo/fix/openai-codex-turn-metadata-ascii

fix(openai): 改写 Codex turn metadata 时保留非 ASCII 转义
This commit is contained in:
Wesley Liddick
2026-09-22 20:25:47 +08:00
committed by GitHub
4 changed files with 131 additions and 4 deletions
@@ -154,7 +154,7 @@ func applyCodexAccountIdentityEmbeddedMetadata(values map[string]any, account *A
if !applyCodexAccountIdentityFields(metadata, account, apiKeyID) {
return false
}
rebuilt, err := json.Marshal(metadata)
rebuilt, err := marshalCodexTurnMetadata(metadata)
if err != nil {
return false
}
@@ -267,7 +267,7 @@ func applyCodexAccountIdentityHeaders(headers http.Header, account *Account, api
if raw := strings.TrimSpace(headers.Get(openAIWSTurnMetadataHeader)); raw != "" {
metadata := map[string]any{}
if err := json.Unmarshal([]byte(raw), &metadata); err == nil && metadata != nil && applyCodexAccountIdentityFields(metadata, account, apiKeyID) {
if rebuilt, err := json.Marshal(metadata); err == nil {
if rebuilt, err := marshalCodexTurnMetadata(metadata); err == nil {
headers.Set(openAIWSTurnMetadataHeader, string(rebuilt))
}
}
@@ -402,7 +402,7 @@ func rewriteCodexTurnMetadataFields(h http.Header, fields map[string]any) {
for k, v := range fields {
metadata[k] = v
}
rebuilt, err := json.Marshal(metadata)
rebuilt, err := marshalCodexTurnMetadata(metadata)
if err != nil {
return
}
@@ -595,7 +595,7 @@ func rewriteClientMetadataEmbeddedTurnMetadata(clientMetadata map[string]any, fi
for k, v := range fields {
metadata[k] = v
}
if rebuilt, err := json.Marshal(metadata); err == nil {
if rebuilt, err := marshalCodexTurnMetadata(metadata); err == nil {
clientMetadata["x-codex-turn-metadata"] = string(rebuilt)
}
}
@@ -0,0 +1,39 @@
package service
import (
"encoding/json"
"unicode/utf16"
)
// Turn metadata is also used as an HTTP header, including when carried in WS JSON.
func marshalCodexTurnMetadata(metadata map[string]any) ([]byte, error) {
raw, err := json.Marshal(metadata)
if err != nil {
return nil, err
}
for i, b := range raw {
if b < 0x7f {
continue
}
out := make([]byte, 0, len(raw)+16)
out = append(out, raw[:i]...)
appendEscape := func(r rune) {
const hex = "0123456789abcdef"
out = append(out, '\\', 'u', hex[r>>12&15], hex[r>>8&15], hex[r>>4&15], hex[r&15])
}
for _, r := range string(raw[i:]) {
switch {
case r < 0x7f:
out = append(out, byte(r))
case r <= 0xffff:
appendEscape(r)
default:
high, low := utf16.EncodeRune(r)
appendEscape(high)
appendEscape(low)
}
}
return out, nil
}
return raw, nil
}
@@ -0,0 +1,88 @@
package service
import (
"encoding/json"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
)
func TestCodexTurnMetadataRewritePreservesHeaderSafeJSON(t *testing.T) {
account := newTestOAuthAccount(1, map[string]any{codexFingerprintModeExtraKey: "session"})
account.Credentials = map[string]any{"chatgpt_account_id": "test-account"}
ids := resolveCodexFingerprintIDsFromRequest(account, http.Header{})
require.NotNil(t, ids)
rewriters := map[string]func(*testing.T, string) string{
"account_header": func(t *testing.T, raw string) string {
h := make(http.Header)
h.Set(openAIWSTurnMetadataHeader, raw)
applyCodexAccountIdentityHeaders(h, account, 77)
return h.Get(openAIWSTurnMetadataHeader)
},
"account_body_map": func(t *testing.T, raw string) string {
cm := map[string]any{openAIWSTurnMetadataHeader: raw}
require.True(t, applyCodexAccountIdentityClientMetadataMap(map[string]any{"client_metadata": cm}, account, 77))
value, ok := cm[openAIWSTurnMetadataHeader].(string)
require.True(t, ok)
return value
},
"account_body_raw": func(t *testing.T, raw string) string {
body, err := json.Marshal(map[string]any{"client_metadata": map[string]any{openAIWSTurnMetadataHeader: raw}})
require.NoError(t, err)
updated, changed, err := applyCodexAccountIdentityClientMetadataRaw(body, account, 77)
require.NoError(t, err)
require.True(t, changed)
return gjson.GetBytes(updated, "client_metadata."+openAIWSTurnMetadataHeader).String()
},
"fingerprint_header": func(t *testing.T, raw string) string {
h := make(http.Header)
h.Set(openAIWSTurnMetadataHeader, raw)
applyCodexFingerprintHeaders(h, ids)
return h.Get(openAIWSTurnMetadataHeader)
},
"fingerprint_body_map": func(t *testing.T, raw string) string {
cm := map[string]any{openAIWSTurnMetadataHeader: raw}
require.True(t, applyCodexFingerprintClientMetadata(map[string]any{"client_metadata": cm}, ids))
value, ok := cm[openAIWSTurnMetadataHeader].(string)
require.True(t, ok)
return value
},
"fingerprint_body_raw": func(t *testing.T, raw string) string {
body, err := json.Marshal(map[string]any{"client_metadata": map[string]any{openAIWSTurnMetadataHeader: raw}})
require.NoError(t, err)
updated, changed, err := applyCodexFingerprintClientMetadataRaw(body, ids)
require.NoError(t, err)
require.True(t, changed)
return gjson.GetBytes(updated, "client_metadata."+openAIWSTurnMetadataHeader).String()
},
}
for _, raw := range []string{
`{"installation_id":"client-install","workspaces":{"C:\\work\\中文🚀":{}}}`,
`{"installation_id":"client-install","workspaces":{"C:\\work\\中文🚀":{"label":"café"}},"literal":"\\u4e2d","quote":"\"\\","controls":"\b\f\n\r\t\u0000\u007f","timestamp":1789302780858,"enabled":true}`,
`{"installation_id":"client-install","workspaces":{"C:\\work\\\u4e2d\u6587\ud83d\ude80":{"label":"caf\u00e9"}},"literal":"\\u4e2d","quote":"\"\\","controls":"\b\f\n\r\t\u0000\u007f","timestamp":1789302780858,"enabled":true}`,
`{"installation_id":"client-install","workspaces":{"C:\\work\\ascii":{}},"literal":"\\u4e2d"}`,
} {
var original map[string]any
require.NoError(t, json.Unmarshal([]byte(raw), &original))
for name, rewrite := range rewriters {
t.Run(name, func(t *testing.T) {
updated := rewrite(t, raw)
var decoded map[string]any
require.NoError(t, json.Unmarshal([]byte(updated), &decoded))
for key, value := range original {
if key != "installation_id" {
require.Equal(t, value, decoded[key], key)
}
}
require.NotEqual(t, original["installation_id"], decoded["installation_id"])
for _, b := range []byte(updated) {
require.True(t, b >= 0x20 && b < 0x7f, "non-printable ASCII header byte: 0x%02x", b)
}
})
}
}
}