diff --git a/dto/openai_compaction.go b/dto/openai_compaction.go index f19df09ceb..81a2c63f7b 100644 --- a/dto/openai_compaction.go +++ b/dto/openai_compaction.go @@ -9,7 +9,7 @@ import ( type OpenAIResponsesCompactionResponse struct { ID string `json:"id"` Object string `json:"object"` - CreatedAt int `json:"created_at"` + CreatedAt UnixTimestamp `json:"created_at"` Output json.RawMessage `json:"output"` Usage *Usage `json:"usage"` Error any `json:"error,omitempty"` diff --git a/dto/openai_response.go b/dto/openai_response.go index a405b97437..179881baa0 100644 --- a/dto/openai_response.go +++ b/dto/openai_response.go @@ -266,7 +266,7 @@ type OutputTokenDetails struct { type OpenAIResponsesResponse struct { ID string `json:"id"` Object string `json:"object"` - CreatedAt int `json:"created_at"` + CreatedAt UnixTimestamp `json:"created_at"` Status string `json:"status"` Error any `json:"error,omitempty"` IncompleteDetails *IncompleteDetails `json:"incomplete_details,omitempty"` diff --git a/dto/values.go b/dto/values.go index 860d5fae7c..1aa26e1136 100644 --- a/dto/values.go +++ b/dto/values.go @@ -1,7 +1,9 @@ package dto import ( + "bytes" "encoding/json" + "fmt" "strconv" ) @@ -29,6 +31,41 @@ func (i IntValue) MarshalJSON() ([]byte, error) { return json.Marshal(int(i)) } +type UnixTimestamp int64 + +func (t *UnixTimestamp) UnmarshalJSON(data []byte) error { + trimmed := bytes.TrimSpace(data) + if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) { + return nil + } + if trimmed[0] == '"' { + return fmt.Errorf("timestamp must be json number, got string") + } + + var n json.Number + if err := json.Unmarshal(trimmed, &n); err != nil { + return err + } + if i, err := n.Int64(); err == nil { + *t = UnixTimestamp(i) + return nil + } + f, err := n.Float64() + if err != nil { + return err + } + *t = UnixTimestamp(int64(f)) + return nil +} + +func (t UnixTimestamp) MarshalJSON() ([]byte, error) { + return json.Marshal(int64(t)) +} + +func (t UnixTimestamp) Int64() int64 { + return int64(t) +} + type BoolValue bool func (b *BoolValue) UnmarshalJSON(data []byte) error { diff --git a/dto/values_test.go b/dto/values_test.go new file mode 100644 index 0000000000..8c8d49b546 --- /dev/null +++ b/dto/values_test.go @@ -0,0 +1,61 @@ +package dto + +import ( + "testing" + + "github.com/QuantumNous/new-api/common" + "github.com/stretchr/testify/require" +) + +func TestUnixTimestampUnmarshalJSON(t *testing.T) { + testCases := []struct { + name string + input string + expected int64 + }{ + { + name: "integer number", + input: `1768488160`, + expected: 1768488160, + }, + { + name: "scientific number", + input: `1.76848816E9`, + expected: 1768488160, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var ts UnixTimestamp + err := common.UnmarshalJsonStr(tc.input, &ts) + require.NoError(t, err) + require.Equal(t, tc.expected, ts.Int64()) + }) + } +} + +func TestUnixTimestampUnmarshalJSONRejectsString(t *testing.T) { + var ts UnixTimestamp + err := common.UnmarshalJsonStr(`"1768488160"`, &ts) + require.Error(t, err) +} + +func TestResponsesStreamResponseCreatedAtScientificNotation(t *testing.T) { + payload := `{"response":{"id":"resp_1","created_at":1.76848816E9}}` + + var resp ResponsesStreamResponse + err := common.UnmarshalJsonStr(payload, &resp) + require.NoError(t, err) + require.NotNil(t, resp.Response) + require.Equal(t, int64(1768488160), resp.Response.CreatedAt.Int64()) +} + +func TestResponsesCompactionResponseCreatedAtScientificNotation(t *testing.T) { + payload := `{"id":"resp_1","object":"response","created_at":1.76848816E9}` + + var resp OpenAIResponsesCompactionResponse + err := common.UnmarshalJsonStr(payload, &resp) + require.NoError(t, err) + require.Equal(t, int64(1768488160), resp.CreatedAt.Int64()) +} diff --git a/relay/channel/openai/chat_via_responses.go b/relay/channel/openai/chat_via_responses.go index 1aa06473c3..33e5ebc929 100644 --- a/relay/channel/openai/chat_via_responses.go +++ b/relay/channel/openai/chat_via_responses.go @@ -314,7 +314,7 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo model = streamResp.Response.Model } if streamResp.Response.CreatedAt != 0 { - createAt = int64(streamResp.Response.CreatedAt) + createAt = streamResp.Response.CreatedAt.Int64() } } @@ -439,7 +439,7 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo model = streamResp.Response.Model } if streamResp.Response.CreatedAt != 0 { - createAt = int64(streamResp.Response.CreatedAt) + createAt = streamResp.Response.CreatedAt.Int64() } if streamResp.Response.Usage != nil { if streamResp.Response.Usage.InputTokens != 0 {