package helps import ( "crypto/sha256" "encoding/hex" "strconv" "strings" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson") const ( codexInputItemIDLimit = 64 codexMessageItemIDPrefix = "msg" codexReasoningItemIDPrefix = "rs" codexFunctionCallItemIDPrefix = "fc" codexCustomToolCallItemIDPrefix = "ctc" codexCustomToolCallOutputItemIDPrefix = "ctco" codexInputItemIDOccupied uint8 = 1 << 0 codexInputItemIDPreserved uint8 = 1 << 1) // SanitizeCodexInputItemIDs normalizes supported input item IDs for Codex, removes encrypted// reasoning items whose IDs exceed the Codex limit, and deterministically shortens// other overlong input item IDs.func SanitizeCodexInputItemIDs(body []byte) []byte { input := util.GetGJSONBytesNoCopy(body, "input") if !input.IsArray() { return body } items := input.Array() idStates := make(map[string]uint8, len(items)) for _, item := range items { if shouldDropCodexEncryptedReasoningItem(item) { continue } itemID := item.Get("id") if itemID.Type != gjson.String { continue } originalID := itemID.String() id := normalizeCodexInputItemID(item, originalID) state := idStates[id] if id == originalID { state |= codexInputItemIDPreserved } if len([]rune(id)) <= codexInputItemIDLimit { state |= codexInputItemIDOccupied } if state != 0 { idStates[id] = state } } var mapped map[string]string var collisionMapped map[string]string rebuilt := make([]string, 0, len(items)) changed := false for _, item := range items { if shouldDropCodexEncryptedReasoningItem(item) { changed = true continue } raw := item.Raw itemID := item.Get("id") if itemID.Type == gjson.String { originalID := itemID.String() id := normalizeCodexInputItemID(item, originalID) if id != originalID && idStates[id]&codexInputItemIDPreserved != 0 { collisionID, ok := collisionMapped[id] if !ok { for attempt := 0; ; attempt++ { collisionID = codexInputItemIDWithHashSuffix(id, attempt) if idStates[collisionID]&codexInputItemIDOccupied != 0 { continue } if collisionMapped == nil { collisionMapped = make(map[string]string) } collisionMapped[id] = collisionID idStates[collisionID] |= codexInputItemIDOccupied break } } id = collisionID } if len([]rune(id)) > codexInputItemIDLimit { shortened, ok := mapped[id] if !ok { shortened = shortenCodexInputItemID(id) for attempt := 1; ; attempt++ { if idStates[shortened]&codexInputItemIDOccupied == 0 { break } shortened = shortenCodexInputItemIDWithAttempt(id, attempt) } if mapped == nil { mapped = make(map[string]string) } mapped[id] = shortened idStates[shortened] |= codexInputItemIDOccupied } id = shortened } if id != originalID { next, errSet := sjson.SetBytes([]byte(raw), "id", id) if errSet == nil { raw = string(next) changed = true } } } rebuilt = append(rebuilt, raw) } if !changed { return body } updated, errSet := sjson.SetRawBytes(body, "input", []byte("["+strings.Join(rebuilt, ",")+"]")) if errSet != nil { return body } return updated} func normalizeCodexInputItemID(item gjson.Result, id string) string { var prefix string switch item.Get("type").String() { case "message": prefix = codexMessageItemIDPrefix case "reasoning": prefix = codexReasoningItemIDPrefix case "function_call": prefix = codexFunctionCallItemIDPrefix case "custom_tool_call": prefix = codexCustomToolCallItemIDPrefix case "custom_tool_call_output": prefix = codexCustomToolCallOutputItemIDPrefix default: return id } if id == "" || strings.HasPrefix(id, prefix) { return id } return prefix + "_" + id} func shouldDropCodexEncryptedReasoningItem(item gjson.Result) bool { if item.Get("type").String() != "reasoning" { return false } itemID := item.Get("id") if itemID.Type != gjson.String || len([]rune(itemID.String())) <= codexInputItemIDLimit { return false } encryptedContent := item.Get("encrypted_content") return encryptedContent.Type == gjson.String && encryptedContent.String() != ""} func shortenCodexInputItemID(id string) string { return shortenCodexInputItemIDWithAttempt(id, 0)} func shortenCodexInputItemIDWithAttempt(id string, attempt int) string { runes := []rune(id) if len(runes) <= codexInputItemIDLimit { return id } return codexInputItemIDWithHashSuffixRunes(id, runes, attempt)} func codexInputItemIDWithHashSuffix(id string, attempt int) string { return codexInputItemIDWithHashSuffixRunes(id, []rune(id), attempt)} func codexInputItemIDWithHashSuffixRunes(id string, runes []rune, attempt int) string { hashInput := id if attempt > 0 { hashInput += "\x00" + strconv.Itoa(attempt) } sum := sha256.Sum256([]byte(hashInput)) suffix := "_" + hex.EncodeToString(sum[:8]) prefixLength := codexInputItemIDLimit - len(suffix) if len(runes) < prefixLength { prefixLength = len(runes) } return string(runes[:prefixLength]) + suffix}
package helps
import (
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
const (
codexInputItemIDLimit = 64
codexMessageItemIDPrefix = "msg"
codexReasoningItemIDPrefix = "rs"
codexFunctionCallItemIDPrefix = "fc"
codexCustomToolCallItemIDPrefix = "ctc"
codexCustomToolCallOutputItemIDPrefix = "ctco"
codexInputItemIDOccupied uint8 = 1 << 0
codexInputItemIDPreserved uint8 = 1 << 1
)
// SanitizeCodexInputItemIDs normalizes supported input item IDs for Codex, removes encrypted
// reasoning items whose IDs exceed the Codex limit, and deterministically shortens
// other overlong input item IDs.
func SanitizeCodexInputItemIDs(body []byte) []byte {
input := util.GetGJSONBytesNoCopy(body, "input")
if !input.IsArray() {
return body
}
items := input.Array()
idStates := make(map[string]uint8, len(items))
for _, item := range items {
if shouldDropCodexEncryptedReasoningItem(item) {
continue
}
itemID := item.Get("id")
if itemID.Type != gjson.String {
continue
}
originalID := itemID.String()
id := normalizeCodexInputItemID(item, originalID)
state := idStates[id]
if id == originalID {
state |= codexInputItemIDPreserved
}
if len([]rune(id)) <= codexInputItemIDLimit {
state |= codexInputItemIDOccupied
}
if state != 0 {
idStates[id] = state
}
}
var mapped map[string]string
var collisionMapped map[string]string
rebuilt := make([]string, 0, len(items))
changed := false
for _, item := range items {
if shouldDropCodexEncryptedReasoningItem(item) {
changed = true
continue
}
raw := item.Raw
itemID := item.Get("id")
if itemID.Type == gjson.String {
originalID := itemID.String()
id := normalizeCodexInputItemID(item, originalID)
if id != originalID && idStates[id]&codexInputItemIDPreserved != 0 {
collisionID, ok := collisionMapped[id]
if !ok {
for attempt := 0; ; attempt++ {
collisionID = codexInputItemIDWithHashSuffix(id, attempt)
if idStates[collisionID]&codexInputItemIDOccupied != 0 {
continue
}
if collisionMapped == nil {
collisionMapped = make(map[string]string)
}
collisionMapped[id] = collisionID
idStates[collisionID] |= codexInputItemIDOccupied
break
}
}
id = collisionID
}
if len([]rune(id)) > codexInputItemIDLimit {
shortened, ok := mapped[id]
if !ok {
shortened = shortenCodexInputItemID(id)
for attempt := 1; ; attempt++ {
if idStates[shortened]&codexInputItemIDOccupied == 0 {
break
}
shortened = shortenCodexInputItemIDWithAttempt(id, attempt)
}
if mapped == nil {
mapped = make(map[string]string)
}
mapped[id] = shortened
idStates[shortened] |= codexInputItemIDOccupied
}
id = shortened
}
if id != originalID {
next, errSet := sjson.SetBytes([]byte(raw), "id", id)
if errSet == nil {
raw = string(next)
changed = true
}
}
}
rebuilt = append(rebuilt, raw)
}
if !changed {
return body
}
updated, errSet := sjson.SetRawBytes(body, "input", []byte("["+strings.Join(rebuilt, ",")+"]"))
if errSet != nil {
return body
}
return updated
}
func normalizeCodexInputItemID(item gjson.Result, id string) string {
var prefix string
switch item.Get("type").String() {
case "message":
prefix = codexMessageItemIDPrefix
case "reasoning":
prefix = codexReasoningItemIDPrefix
case "function_call":
prefix = codexFunctionCallItemIDPrefix
case "custom_tool_call":
prefix = codexCustomToolCallItemIDPrefix
case "custom_tool_call_output":
prefix = codexCustomToolCallOutputItemIDPrefix
default:
return id
}
if id == "" || strings.HasPrefix(id, prefix) {
return id
}
return prefix + "_" + id
}
func shouldDropCodexEncryptedReasoningItem(item gjson.Result) bool {
if item.Get("type").String() != "reasoning" {
return false
}
itemID := item.Get("id")
if itemID.Type != gjson.String || len([]rune(itemID.String())) <= codexInputItemIDLimit {
return false
}
encryptedContent := item.Get("encrypted_content")
return encryptedContent.Type == gjson.String && encryptedContent.String() != ""
}
func shortenCodexInputItemID(id string) string {
return shortenCodexInputItemIDWithAttempt(id, 0)
}
func shortenCodexInputItemIDWithAttempt(id string, attempt int) string {
runes := []rune(id)
if len(runes) <= codexInputItemIDLimit {
return id
}
return codexInputItemIDWithHashSuffixRunes(id, runes, attempt)
}
func codexInputItemIDWithHashSuffix(id string, attempt int) string {
return codexInputItemIDWithHashSuffixRunes(id, []rune(id), attempt)
}
func codexInputItemIDWithHashSuffixRunes(id string, runes []rune, attempt int) string {
hashInput := id
if attempt > 0 {
hashInput += "\x00" + strconv.Itoa(attempt)
}
sum := sha256.Sum256([]byte(hashInput))
suffix := "_" + hex.EncodeToString(sum[:8])
prefixLength := codexInputItemIDLimit - len(suffix)
if len(runes) < prefixLength {
prefixLength = len(runes)
}
return string(runes[:prefixLength]) + suffix
}