feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
aa6c3f50f4
commit
ceddfd3d6b
828 files changed
+122253
-13702
No files matched your search
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -22,13 +21,6 @@ import (
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
func updateOpenAIImageCount(info *relaycommon.RelayInfo, count int64) {
|
||||
if info == nil || !info.PriceData.UsePrice || count <= 0 || count > int64(dto.MaxImageN) {
|
||||
return
|
||||
}
|
||||
info.PriceData.AddOtherRatio("n", float64(count))
|
||||
}
|
||||
|
||||
// OpenaiImageHandler handles non-streaming OpenAI image responses
|
||||
// (generations/edits), returning the parsed usage for billing.
|
||||
func OpenaiImageHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
|
||||
@@ -49,7 +41,7 @@ func OpenaiImageHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.
|
||||
return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
|
||||
}
|
||||
|
||||
updateOpenAIImageCount(info, gjson.GetBytes(responseBody, "data.#").Int())
|
||||
info.UpdateImageCount(openaiImageResponseCount(responseBody))
|
||||
|
||||
// 写入新的 response body
|
||||
service.IOCopyBytesGracefully(c, resp, responseBody)
|
||||
@@ -59,14 +51,54 @@ func OpenaiImageHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.
|
||||
return &usageResp.Usage, nil
|
||||
}
|
||||
|
||||
// openaiImageResponseCount counts billable images in an OpenAI-format image
|
||||
// response body. An object-shaped data is one image when it carries url or
|
||||
// b64_json. For arrays the count is the larger of the url-bearing and the
|
||||
// b64_json-bearing entry counts: a standard response uses one response_format
|
||||
// so this equals the entry count, an upstream that splits one image into a url
|
||||
// entry and a b64_json entry bills once, and entries without any image payload
|
||||
// bill nothing. A zero result leaves the requested quantity in place because
|
||||
// UpdateImageCount ignores non-positive counts.
|
||||
func openaiImageResponseCount(responseBody []byte) int64 {
|
||||
data := gjson.GetBytes(responseBody, "data")
|
||||
if data.IsObject() {
|
||||
if openaiImageDataHasField(data, "url") || openaiImageDataHasField(data, "b64_json") {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if !data.IsArray() {
|
||||
return 0
|
||||
}
|
||||
var urls, b64s int64
|
||||
data.ForEach(func(_, item gjson.Result) bool {
|
||||
if openaiImageDataHasField(item, "url") {
|
||||
urls++
|
||||
}
|
||||
if openaiImageDataHasField(item, "b64_json") {
|
||||
b64s++
|
||||
}
|
||||
return true
|
||||
})
|
||||
return max(urls, b64s)
|
||||
}
|
||||
|
||||
// openaiImageDataHasField reports whether an image data entry carries a
|
||||
// non-empty string value for field.
|
||||
func openaiImageDataHasField(item gjson.Result, field string) bool {
|
||||
value := item.Get(field)
|
||||
return value.Type == gjson.String && value.Raw != `""`
|
||||
}
|
||||
|
||||
// normalizeOpenAIUsage maps the OpenAI Images usage shape (input_tokens /
|
||||
// output_tokens / input_tokens_details) onto the canonical prompt/completion
|
||||
// fields. It is used only on the OpenAI image relay paths (generations/edits,
|
||||
// streaming and non-streaming): the image API never returns prompt_tokens /
|
||||
// completion_tokens, so the overwrite (=) semantics here are equivalent to the
|
||||
// previous additive (+=) behavior while avoiding any future double-counting if
|
||||
// both field sets are ever populated. Do not reuse this on chat/embedding paths
|
||||
// without revisiting the overwrite semantics.
|
||||
// output_tokens / input_tokens_details / output_tokens_details) onto the
|
||||
// canonical prompt/completion fields. It is used only on the OpenAI image
|
||||
// relay paths (generations/edits, streaming and non-streaming): the image
|
||||
// API never returns prompt_tokens / completion_tokens, so the overwrite (=)
|
||||
// semantics here are equivalent to the previous additive (+=) behavior while
|
||||
// avoiding any future double-counting if both field sets are ever populated.
|
||||
// Do not reuse this on chat/embedding paths without revisiting the overwrite
|
||||
// semantics.
|
||||
func normalizeOpenAIUsage(usage *dto.Usage) {
|
||||
if usage == nil {
|
||||
return
|
||||
@@ -78,12 +110,10 @@ func normalizeOpenAIUsage(usage *dto.Usage) {
|
||||
usage.CompletionTokens = usage.OutputTokens
|
||||
}
|
||||
if usage.InputTokensDetails != nil {
|
||||
usage.PromptTokensDetails.CachedTokens = usage.InputTokensDetails.CachedTokens
|
||||
usage.PromptTokensDetails.CachedCreationTokens = usage.InputTokensDetails.CachedCreationTokens
|
||||
usage.PromptTokensDetails.CacheWriteTokens = usage.InputTokensDetails.CacheWriteTokens
|
||||
usage.PromptTokensDetails.ImageTokens = usage.InputTokensDetails.ImageTokens
|
||||
usage.PromptTokensDetails.TextTokens = usage.InputTokensDetails.TextTokens
|
||||
usage.PromptTokensDetails.AudioTokens = usage.InputTokensDetails.AudioTokens
|
||||
usage.PromptTokensDetails = usage.InputTokensDetails.Clone()
|
||||
}
|
||||
if usage.OutputTokensDetails != nil {
|
||||
usage.CompletionTokenDetails = *usage.OutputTokensDetails
|
||||
}
|
||||
if usage.TotalTokens == 0 {
|
||||
usage.TotalTokens = usage.PromptTokens + usage.CompletionTokens
|
||||
@@ -156,12 +186,8 @@ func OpenaiImageStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
|
||||
if info.StreamStatus != nil {
|
||||
upstreamFinished := info.StreamStatus.EndReason == relaycommon.StreamEndReasonDone ||
|
||||
info.StreamStatus.EndReason == relaycommon.StreamEndReasonEOF
|
||||
requestedN := 1.0
|
||||
if n, ok := info.PriceData.OtherRatios()["n"]; ok {
|
||||
requestedN = n
|
||||
}
|
||||
if upstreamFinished || float64(completedImages) > requestedN {
|
||||
updateOpenAIImageCount(info, completedImages)
|
||||
if upstreamFinished || completedImages > int64(info.RequestedImageCount()) {
|
||||
info.UpdateImageCount(completedImages)
|
||||
}
|
||||
}
|
||||
return usage, nil
|
||||
@@ -252,8 +278,7 @@ func openaiImageJSONAsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo,
|
||||
normalizeOpenAIUsage(&usageResp.Usage)
|
||||
applyUsagePostProcessing(info, &usageResp.Usage, responseBody)
|
||||
|
||||
imageCount := gjson.GetBytes(responseBody, "data.#").Int()
|
||||
updateOpenAIImageCount(info, imageCount)
|
||||
info.UpdateImageCount(openaiImageResponseCount(responseBody))
|
||||
|
||||
helper.SetEventStreamHeaders(c)
|
||||
c.Status(http.StatusOK)
|
||||
@@ -275,8 +300,15 @@ func openaiImageJSONAsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo,
|
||||
}
|
||||
}
|
||||
|
||||
for i := range imageCount {
|
||||
image := gjson.GetBytes(responseBody, "data."+strconv.FormatInt(i, 10))
|
||||
// gjson.Result.Array returns the element list for an array and a single
|
||||
// element for an object-shaped data, keeping document-relative indexes so
|
||||
// the zero-copy field forwarding below stays valid for both shapes.
|
||||
// Entries without url or b64_json carry no image and are not forwarded.
|
||||
emitted := 0
|
||||
for _, image := range gjson.GetBytes(responseBody, "data").Array() {
|
||||
if !openaiImageDataHasField(image, "url") && !openaiImageDataHasField(image, "b64_json") {
|
||||
continue
|
||||
}
|
||||
payload := []byte(`{"type":"image_generation.completed"}`)
|
||||
payload, err = sjson.SetBytes(payload, "created_at", created)
|
||||
if err != nil {
|
||||
@@ -311,6 +343,7 @@ func openaiImageJSONAsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo,
|
||||
}
|
||||
return &usageResp.Usage, nil
|
||||
}
|
||||
emitted++
|
||||
}
|
||||
if err := writeOpenaiImageStreamDone(c); err != nil {
|
||||
if info != nil && info.StreamStatus != nil {
|
||||
@@ -319,7 +352,7 @@ func openaiImageJSONAsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo,
|
||||
return &usageResp.Usage, nil
|
||||
}
|
||||
if info != nil {
|
||||
info.ReceivedResponseCount += int(imageCount)
|
||||
info.ReceivedResponseCount += emitted
|
||||
if info.StreamStatus == nil {
|
||||
info.StreamStatus = relaycommon.NewStreamStatus()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user