Files
CLIProxyAPI/internal/runtime/executor/helps/codex_input_ids.go

145 lines
3.5 KiB
Go

package helps
import (
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
const (
codexInputItemIDLimit = 64
codexMessageItemIDPrefix = "msg"
codexReasoningItemIDPrefix = "rs"
codexFunctionCallItemIDPrefix = "fc"
)
// 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 := gjson.GetBytes(body, "input")
if !input.IsArray() {
return body
}
items := input.Array()
occupied := make(map[string]struct{}, len(items))
for _, item := range items {
if shouldDropCodexEncryptedReasoningItem(item) {
continue
}
itemID := item.Get("id")
if itemID.Type != gjson.String {
continue
}
id := normalizeCodexInputItemID(item, itemID.String())
if len([]rune(id)) <= codexInputItemIDLimit {
occupied[id] = struct{}{}
}
}
mapped := make(map[string]string, len(items))
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 len([]rune(id)) > codexInputItemIDLimit {
shortened, ok := mapped[id]
if !ok {
shortened = shortenCodexInputItemID(id)
for attempt := 1; ; attempt++ {
if _, exists := occupied[shortened]; !exists {
break
}
shortened = shortenCodexInputItemIDWithAttempt(id, attempt)
}
mapped[id] = shortened
occupied[shortened] = struct{}{}
}
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
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
}
hashInput := id
if attempt > 0 {
hashInput += "\x00" + strconv.Itoa(attempt)
}
sum := sha256.Sum256([]byte(hashInput))
suffix := "_" + hex.EncodeToString(sum[:8])
prefixLength := codexInputItemIDLimit - len(suffix)
return string(runes[:prefixLength]) + suffix
}