| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- package service
- import (
- "fmt"
- "strings"
- "github.com/tidwall/gjson"
- "github.com/tidwall/sjson"
- )
- func openAIResponsesInputItemIDPrefix(itemType string) (string, bool) {
- switch strings.TrimSpace(itemType) {
- case "message":
- return "msg", true
- case "reasoning":
- return "rs", true
- case "web_search_call":
- return "ws", true
- case "custom_tool_call":
- return openAIResponsesToolCallIDPrefix(itemType), true
- case "tool_search_call":
- return openAIResponsesToolCallIDPrefix(itemType), true
- case "custom_tool_call_output":
- // Although custom calls use ctc IDs, OpenAI validates replayed custom
- // call output item IDs against the generic fc namespace.
- return "fc", true
- default:
- if isCodexToolCallInputType(itemType) {
- return openAIResponsesToolCallIDPrefix(itemType), true
- }
- return "", false
- }
- }
- func openAIResponsesToolCallIDPrefix(itemType string) string {
- switch strings.TrimSpace(itemType) {
- case "custom_tool_call", "custom_tool_call_output":
- return "ctc"
- case "tool_search_call", "tool_search_output":
- return "tsc"
- default:
- return "fc"
- }
- }
- // Invalid replayed IDs are removed rather than rewritten because a fabricated
- // ID may point at a different upstream object.
- func shouldStripOpenAIResponsesInputItemID(itemType, id string) bool {
- prefix, constrained := openAIResponsesInputItemIDPrefix(itemType)
- if !constrained {
- return false
- }
- return id == "" || !strings.HasPrefix(id, prefix)
- }
- func shouldStripOpenAIResponsesNonPairCallID(itemType string) bool {
- switch strings.TrimSpace(itemType) {
- case "message", "reasoning", "image_generation_call":
- return true
- default:
- return false
- }
- }
- func sanitizeOpenAIResponsesInputItemIDs(body []byte) ([]byte, bool, error) {
- input := gjson.GetBytes(body, "input")
- if !input.IsArray() {
- return body, false, nil
- }
- type inputItem struct {
- body []byte
- stripID bool
- stripCallID bool
- }
- items := make([]inputItem, 0)
- input.ForEach(func(_, item gjson.Result) bool {
- parsed := inputItem{body: []byte(item.Raw)}
- if item.IsObject() {
- itemType := item.Get("type")
- id := item.Get("id")
- trimmedItemType := strings.TrimSpace(itemType.String())
- parsed.stripCallID = item.Get("call_id").Exists() && shouldStripOpenAIResponsesNonPairCallID(trimmedItemType)
- if id.Type == gjson.String {
- parsed.stripID = shouldStripOpenAIResponsesInputItemID(trimmedItemType, id.String())
- }
- }
- items = append(items, parsed)
- return true
- })
- hasSanitization := false
- for _, item := range items {
- if item.stripID || item.stripCallID {
- hasSanitization = true
- break
- }
- }
- if !hasSanitization {
- return body, false, nil
- }
- rebuiltItems := make([][]byte, 0, len(items))
- for index, item := range items {
- itemBody := item.body
- if item.stripID {
- var err error
- itemBody, err = sjson.DeleteBytes(itemBody, "id")
- if err != nil {
- return nil, false, fmt.Errorf("delete input.%d.id: %w", index, err)
- }
- }
- if item.stripCallID {
- var err error
- itemBody, err = sjson.DeleteBytes(itemBody, "call_id")
- if err != nil {
- return nil, false, fmt.Errorf("delete input.%d.call_id: %w", index, err)
- }
- }
- rebuiltItems = append(rebuiltItems, itemBody)
- }
- rebuiltInput := make([]byte, 0, len(input.Raw))
- rebuiltInput = append(rebuiltInput, '[')
- for i, item := range rebuiltItems {
- if i > 0 {
- rebuiltInput = append(rebuiltInput, ',')
- }
- rebuiltInput = append(rebuiltInput, item...)
- }
- rebuiltInput = append(rebuiltInput, ']')
- sanitized, err := sjson.SetRawBytes(body, "input", rebuiltInput)
- if err != nil {
- return nil, false, fmt.Errorf("replace sanitized input: %w", err)
- }
- return sanitized, true, nil
- }
|