From 798f4ef80d7722e187e66d389bd088cc7c813216 Mon Sep 17 00:00:00 2001 From: zijiren233 Date: Wed, 20 May 2026 21:17:18 +0800 Subject: [PATCH 1/2] feat: add more tokens --- core/common/consume/consume.go | 20 ++ core/common/consume/consume_test.go | 36 +++ core/controller/dashboard.go | 12 +- core/controller/log_export.go | 8 + core/docs/docs.go | 275 +++++++++++++++++++++- core/docs/swagger.json | 275 +++++++++++++++++++++- core/docs/swagger.yaml | 193 ++++++++++++++- core/model/async_usage.go | 2 + core/model/summary.go | 8 + core/model/summary_test.go | 4 +- core/model/usage.go | 32 +++ core/relay/controller/dohelper.go | 8 + core/relay/model/chat.go | 45 +++- core/relay/model/embed.go | 2 + core/relay/model/gemini.go | 59 ++++- core/relay/model/response.go | 24 +- core/relay/model/usage_conversion_test.go | 77 ++++++ core/relay/plugin/streamfake/fake.go | 77 ++++++ core/relay/plugin/streamfake/fake_test.go | 42 ++++ 19 files changed, 1155 insertions(+), 44 deletions(-) diff --git a/core/common/consume/consume.go b/core/common/consume/consume.go index 99bdf1d7..05a6b20c 100644 --- a/core/common/consume/consume.go +++ b/core/common/consume/consume.go @@ -205,6 +205,10 @@ func CalculateAmountDetail( inputTokens -= usage.AudioInputTokens } + if modelPrice.VideoInputPrice > 0 { + inputTokens -= usage.VideoInputTokens + } + if modelPrice.CachedPrice > 0 { inputTokens -= usage.CachedTokens } @@ -218,6 +222,10 @@ func CalculateAmountDetail( outputTokens -= usage.ImageOutputTokens } + if modelPrice.AudioOutputPrice > 0 { + outputTokens -= usage.AudioOutputTokens + } + outputPrice := float64(modelPrice.OutputPrice) outputPriceUnit := modelPrice.GetOutputPriceUnit() @@ -240,6 +248,10 @@ func CalculateAmountDetail( Mul(decimal.NewFromFloat(float64(modelPrice.AudioInputPrice))). Div(decimal.NewFromInt(modelPrice.GetAudioInputPriceUnit())) + videoInputAmount := decimal.NewFromInt(int64(usage.VideoInputTokens)). + Mul(decimal.NewFromFloat(float64(modelPrice.VideoInputPrice))). + Div(decimal.NewFromInt(modelPrice.GetVideoInputPriceUnit())) + cachedAmount := decimal.NewFromInt(int64(usage.CachedTokens)). Mul(decimal.NewFromFloat(float64(modelPrice.CachedPrice))). Div(decimal.NewFromInt(modelPrice.GetCachedPriceUnit())) @@ -260,22 +272,30 @@ func CalculateAmountDetail( Mul(decimal.NewFromFloat(float64(modelPrice.ImageOutputPrice))). Div(decimal.NewFromInt(modelPrice.GetImageOutputPriceUnit())) + audioOutputAmount := decimal.NewFromInt(int64(usage.AudioOutputTokens)). + Mul(decimal.NewFromFloat(float64(modelPrice.AudioOutputPrice))). + Div(decimal.NewFromInt(modelPrice.GetAudioOutputPriceUnit())) + usedAmount := inputAmount. Add(imageInputAmount). Add(audioInputAmount). + Add(videoInputAmount). Add(cachedAmount). Add(cacheCreationAmount). Add(webSearchAmount). Add(outputAmount). Add(imageOutputAmount). + Add(audioOutputAmount). InexactFloat64() return model.Amount{ InputAmount: inputAmount.InexactFloat64(), ImageInputAmount: imageInputAmount.InexactFloat64(), AudioInputAmount: audioInputAmount.InexactFloat64(), + VideoInputAmount: videoInputAmount.InexactFloat64(), OutputAmount: outputAmount.InexactFloat64(), ImageOutputAmount: imageOutputAmount.InexactFloat64(), + AudioOutputAmount: audioOutputAmount.InexactFloat64(), CachedAmount: cachedAmount.InexactFloat64(), CacheCreationAmount: cacheCreationAmount.InexactFloat64(), WebSearchAmount: webSearchAmount.InexactFloat64(), diff --git a/core/common/consume/consume_test.go b/core/common/consume/consume_test.go index 92b94b0e..496de2ab 100644 --- a/core/common/consume/consume_test.go +++ b/core/common/consume/consume_test.go @@ -99,6 +99,42 @@ func TestCalculateAmount(t *testing.T) { }, want: 0.019, // 0.001 * 1000/1000 + 0.004 * (3000-1000)/1000 + 0.01 * 1000/1000 }, + { + name: "Audio Input and Output Pricing", + code: http.StatusOK, + usage: model.Usage{ + InputTokens: 2000, + AudioInputTokens: 500, + OutputTokens: 3000, + AudioOutputTokens: 1000, + }, + price: model.Price{ + InputPrice: 0.001, + AudioInputPrice: 0.003, + OutputPrice: 0.004, + AudioOutputPrice: 0.01, + }, + want: 0.021, // text in 0.0015 + audio in 0.0015 + text out 0.008 + audio out 0.01 + }, + { + name: "Video Input Pricing", + code: http.StatusOK, + usage: model.Usage{ + InputTokens: 3000, + ImageInputTokens: 500, + AudioInputTokens: 600, + VideoInputTokens: 1000, + OutputTokens: 2000, + }, + price: model.Price{ + InputPrice: 0.001, + ImageInputPrice: 0.003, + AudioInputPrice: 0.004, + VideoInputPrice: 0.008, + OutputPrice: 0.002, + }, + want: 0.0168, // text in 0.0009 + image in 0.0015 + audio in 0.0024 + video in 0.008 + text out 0.004 + }, { name: "Cached Token Pricing", code: http.StatusOK, diff --git a/core/controller/dashboard.go b/core/controller/dashboard.go index 1437eef5..2452e7d0 100644 --- a/core/controller/dashboard.go +++ b/core/controller/dashboard.go @@ -184,7 +184,7 @@ func fillGaps( // @Param end_timestamp query int64 false "End second timestamp" // @Param timezone query string false "Timezone, default is Local" // @Param timespan query string false "Time span type (minute, hour, day, month)" -// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" +// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" // @Success 200 {object} middleware.APIResponse{data=model.DashboardResponse} // @Router /api/dashboard/ [get] func GetDashboard(c *gin.Context) { @@ -245,7 +245,7 @@ func GetDashboard(c *gin.Context) { // @Param end_timestamp query int64 false "End second timestamp" // @Param timezone query string false "Timezone, default is Local" // @Param timespan query string false "Time span type (minute, hour, day, month)" -// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" +// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" // @Success 200 {object} middleware.APIResponse{data=model.GroupDashboardResponse} // @Router /api/dashboard/{group} [get] func GetGroupDashboard(c *gin.Context) { @@ -438,7 +438,7 @@ func GetGroupDashboardModels(c *gin.Context) { // @Param end_timestamp query int64 false "End timestamp" // @Param timezone query string false "Timezone, default is Local" // @Param timespan query string false "Time span type (minute, hour, day, month)" -// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" +// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" // @Success 200 {object} middleware.APIResponse{data=[]model.TimeSummaryDataV2} // @Router /api/dashboardv2/ [get] func GetTimeSeriesModelData(c *gin.Context) { @@ -479,7 +479,7 @@ func GetTimeSeriesModelData(c *gin.Context) { // @Param end_timestamp query int64 false "End timestamp" // @Param timezone query string false "Timezone, default is Local" // @Param timespan query string false "Time span type (minute, hour, day, month)" -// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" +// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" // @Success 200 {object} middleware.APIResponse{data=[]model.TimeSummaryDataV2} // @Router /api/dashboardv2/{group} [get] func GetGroupTimeSeriesModelData(c *gin.Context) { @@ -526,7 +526,7 @@ func GetGroupTimeSeriesModelData(c *gin.Context) { // @Param end_timestamp query int64 false "End timestamp" // @Param timezone query string false "Timezone, default is Local" // @Param timespan query string false "Time span type (minute, hour, day, month)" -// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" +// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" // @Success 200 {object} middleware.APIResponse{data=model.DashboardV3Response} // @Router /api/dashboardv3/ [get] func GetTimeSeriesModelDataV3(c *gin.Context) { @@ -577,7 +577,7 @@ func GetTimeSeriesModelDataV3(c *gin.Context) { // @Param end_timestamp query int64 false "End timestamp" // @Param timezone query string false "Timezone, default is Local" // @Param timespan query string false "Time span type (minute, hour, day, month)" -// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" +// @Param fields query string false "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all" // @Success 200 {object} middleware.APIResponse{data=model.DashboardV3Response} // @Router /api/dashboardv3/{group} [get] func GetGroupTimeSeriesModelDataV3(c *gin.Context) { diff --git a/core/controller/log_export.go b/core/controller/log_export.go index 4ab2442e..c01146f4 100644 --- a/core/controller/log_export.go +++ b/core/controller/log_export.go @@ -459,8 +459,10 @@ func buildLogExportHeader(includeChannel, includeRetryAt bool) []string { "input_tokens", "image_input_tokens", "audio_input_tokens", + "video_input_tokens", "output_tokens", "image_output_tokens", + "audio_output_tokens", "cached_tokens", "cache_creation_tokens", "reasoning_tokens", @@ -469,8 +471,10 @@ func buildLogExportHeader(includeChannel, includeRetryAt bool) []string { "input_amount", "image_input_amount", "audio_input_amount", + "video_input_amount", "output_amount", "image_output_amount", + "audio_output_amount", "cached_amount", "cache_creation_amount", "web_search_amount", @@ -532,8 +536,10 @@ func buildLogExportRow( strconv.FormatInt(int64(logItem.Usage.InputTokens), 10), strconv.FormatInt(int64(logItem.Usage.ImageInputTokens), 10), strconv.FormatInt(int64(logItem.Usage.AudioInputTokens), 10), + strconv.FormatInt(int64(logItem.Usage.VideoInputTokens), 10), strconv.FormatInt(int64(logItem.Usage.OutputTokens), 10), strconv.FormatInt(int64(logItem.Usage.ImageOutputTokens), 10), + strconv.FormatInt(int64(logItem.Usage.AudioOutputTokens), 10), strconv.FormatInt(int64(logItem.Usage.CachedTokens), 10), strconv.FormatInt(int64(logItem.Usage.CacheCreationTokens), 10), strconv.FormatInt(int64(logItem.Usage.ReasoningTokens), 10), @@ -542,8 +548,10 @@ func buildLogExportRow( formatFloatForExport(logItem.Amount.InputAmount), formatFloatForExport(logItem.Amount.ImageInputAmount), formatFloatForExport(logItem.Amount.AudioInputAmount), + formatFloatForExport(logItem.Amount.VideoInputAmount), formatFloatForExport(logItem.Amount.OutputAmount), formatFloatForExport(logItem.Amount.ImageOutputAmount), + formatFloatForExport(logItem.Amount.AudioOutputAmount), formatFloatForExport(logItem.Amount.CachedAmount), formatFloatForExport(logItem.Amount.CacheCreationAmount), formatFloatForExport(logItem.Amount.WebSearchAmount), diff --git a/core/docs/docs.go b/core/docs/docs.go index a1ceadfb..04916d1a 100644 --- a/core/docs/docs.go +++ b/core/docs/docs.go @@ -1158,7 +1158,7 @@ const docTemplate = `{ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1248,7 +1248,7 @@ const docTemplate = `{ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1380,7 +1380,7 @@ const docTemplate = `{ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1473,7 +1473,7 @@ const docTemplate = `{ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1559,7 +1559,7 @@ const docTemplate = `{ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1649,7 +1649,7 @@ const docTemplate = `{ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -5649,7 +5649,9 @@ const docTemplate = `{ 50, 51, 52, - 53 + 53, + 54, + 55 ], "type": "integer", "description": "Channel type", @@ -9535,6 +9537,9 @@ const docTemplate = `{ "type": "string" } }, + "skip_tls_verify": { + "type": "boolean" + }, "status": { "type": "integer" }, @@ -9647,6 +9652,9 @@ const docTemplate = `{ } } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -9890,6 +9898,9 @@ const docTemplate = `{ } } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -10555,6 +10566,9 @@ const docTemplate = `{ "format": "float64" } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -10564,6 +10578,9 @@ const docTemplate = `{ "override_limit": { "type": "boolean" }, + "override_max_image_generation_count": { + "type": "boolean" + }, "override_price": { "type": "boolean" }, @@ -10649,6 +10666,9 @@ const docTemplate = `{ } } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -10740,6 +10760,9 @@ const docTemplate = `{ "proxy_url": { "type": "string" }, + "skip_tls_verify": { + "type": "boolean" + }, "type": { "type": "integer" } @@ -10792,6 +10815,9 @@ const docTemplate = `{ "proxy_url": { "type": "string" }, + "skip_tls_verify": { + "type": "boolean" + }, "type": { "type": "integer" } @@ -10954,9 +10980,28 @@ const docTemplate = `{ "src": { "description": "URI pointing to the icon resource (HTTPS URL or data URI)", "type": "string" + }, + "theme": { + "description": "Theme is an optional specifier for the background theme this icon is designed for.\nUse IconThemeLight for light backgrounds or IconThemeDark for dark backgrounds.", + "allOf": [ + { + "$ref": "#/definitions/mcp.IconTheme" + } + ] } } }, + "mcp.IconTheme": { + "type": "string", + "enum": [ + "light", + "dark" + ], + "x-enum-varnames": [ + "IconThemeLight", + "IconThemeDark" + ] + }, "mcp.Meta": { "type": "object", "properties": { @@ -11044,6 +11089,10 @@ const docTemplate = `{ "$ref": "#/definitions/mcp.ToolOutputSchema" } ] + }, + "title": { + "description": "Title is an optional human-readable, UI-friendly display name for the tool.\nIf not provided, clients should use Annotations.Title (if set) and fall back to Name.", + "type": "string" } } }, @@ -11200,6 +11249,9 @@ const docTemplate = `{ "audio_input_amount": { "type": "number" }, + "audio_output_amount": { + "type": "number" + }, "cache_creation_amount": { "type": "number" }, @@ -11221,6 +11273,9 @@ const docTemplate = `{ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, "web_search_amount": { "type": "number" } @@ -11240,6 +11295,21 @@ const docTemplate = `{ } } }, + "model.AsyncUsageStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "x-enum-varnames": [ + "AsyncUsageStatusNone", + "AsyncUsageStatusPending", + "AsyncUsageStatusCompleted", + "AsyncUsageStatusFailed" + ] + }, "model.Channel": { "type": "object", "properties": { @@ -11318,6 +11388,9 @@ const docTemplate = `{ "type": "string" } }, + "skip_tls_verify": { + "type": "boolean" + }, "status": { "type": "integer" }, @@ -11431,7 +11504,9 @@ const docTemplate = `{ 50, 51, 52, - 53 + 53, + 54, + 55 ], "x-enum-varnames": [ "ChannelTypeOpenAI", @@ -11474,7 +11549,9 @@ const docTemplate = `{ "ChannelTypeSangforAICP", "ChannelTypeStreamlake", "ChannelTypeZhipuCoding", - "ChannelTypeFake" + "ChannelTypeFake", + "ChannelTypeAntLing", + "ChannelTypeFakeError" ] }, "model.ChartData": { @@ -11486,6 +11563,12 @@ const docTemplate = `{ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -11585,6 +11668,12 @@ const docTemplate = `{ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -11620,10 +11709,12 @@ const docTemplate = `{ "type": "string", "enum": [ "enabled", + "adaptive", "disabled" ], "x-enum-varnames": [ "ClaudeThinkingTypeEnabled", + "ClaudeThinkingTypeAdaptive", "ClaudeThinkingTypeDisabled" ] }, @@ -11702,6 +11793,9 @@ const docTemplate = `{ "prompt_cache_retention": { "type": "string" }, + "reasoning": { + "$ref": "#/definitions/model.ResponseReasoning" + }, "safety_identifier": { "type": "string" }, @@ -11751,6 +11845,12 @@ const docTemplate = `{ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -11881,6 +11981,12 @@ const docTemplate = `{ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -11934,6 +12040,9 @@ const docTemplate = `{ }, "text_tokens": { "type": "integer" + }, + "video_tokens": { + "type": "integer" } } }, @@ -12048,6 +12157,9 @@ const docTemplate = `{ "model.GeneralOpenAIRequest": { "type": "object", "properties": { + "enable_thinking": { + "type": "boolean" + }, "frequency_penalty": { "type": "number" }, @@ -12087,6 +12199,9 @@ const docTemplate = `{ "prompt_cache_retention": { "type": "string" }, + "reasoning_effort": { + "type": "string" + }, "response_format": { "$ref": "#/definitions/model.ResponseFormat" }, @@ -12117,6 +12232,9 @@ const docTemplate = `{ } ] }, + "thinking_budget": { + "type": "integer" + }, "tool_choice": {}, "tools": { "type": "array", @@ -12263,6 +12381,12 @@ const docTemplate = `{ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -12399,6 +12523,12 @@ const docTemplate = `{ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -12502,6 +12632,9 @@ const docTemplate = `{ "format": "float64" } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -12511,6 +12644,9 @@ const docTemplate = `{ "override_limit": { "type": "boolean" }, + "override_max_image_generation_count": { + "type": "boolean" + }, "override_price": { "type": "boolean" }, @@ -12819,6 +12955,9 @@ const docTemplate = `{ "amount": { "$ref": "#/definitions/model.Amount" }, + "async_usage_status": { + "$ref": "#/definitions/model.AsyncUsageStatus" + }, "channel": { "type": "integer" }, @@ -13014,6 +13153,9 @@ const docTemplate = `{ } } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -13096,7 +13238,8 @@ const docTemplate = `{ "stepfun", "xai", "doc2x", - "jina" + "jina", + "antgroup" ], "x-enum-varnames": [ "ModelOwnerOpenAI", @@ -13130,7 +13273,8 @@ const docTemplate = `{ "ModelOwnerStepFun", "ModelOwnerXAI", "ModelOwnerDoc2x", - "ModelOwnerJina" + "ModelOwnerJina", + "ModelOwnerAntGroup" ] }, "model.Option": { @@ -13223,6 +13367,12 @@ const docTemplate = `{ "audio_input_price_unit": { "type": "integer" }, + "audio_output_price": { + "type": "number" + }, + "audio_output_price_unit": { + "type": "integer" + }, "cache_creation_price": { "type": "number" }, @@ -13275,6 +13425,12 @@ const docTemplate = `{ "thinking_mode_output_price_unit": { "type": "integer" }, + "video_input_price": { + "type": "number" + }, + "video_input_price_unit": { + "type": "integer" + }, "web_search_price": { "type": "number" }, @@ -13322,6 +13478,9 @@ const docTemplate = `{ }, "cached_tokens": { "type": "integer" + }, + "image_tokens": { + "type": "integer" } } }, @@ -13623,6 +13782,9 @@ const docTemplate = `{ "model.Response": { "type": "object", "properties": { + "background": { + "type": "boolean" + }, "created_at": { "type": "integer" }, @@ -13685,6 +13847,9 @@ const docTemplate = `{ "$ref": "#/definitions/model.ResponseText" }, "tool_choice": {}, + "tool_usage": { + "$ref": "#/definitions/model.ResponseToolUsage" + }, "tools": { "type": "array", "items": { @@ -13742,6 +13907,7 @@ const docTemplate = `{ "type": "string", "enum": [ "in_progress", + "queued", "completed", "failed", "incomplete", @@ -13749,6 +13915,7 @@ const docTemplate = `{ ], "x-enum-varnames": [ "ResponseStatusInProgress", + "ResponseStatusQueued", "ResponseStatusCompleted", "ResponseStatusFailed", "ResponseStatusIncomplete", @@ -13786,6 +13953,56 @@ const docTemplate = `{ } } }, + "model.ResponseToolUsage": { + "type": "object", + "properties": { + "image_gen": { + "$ref": "#/definitions/model.ResponseToolUsageImageGen" + }, + "web_search": { + "$ref": "#/definitions/model.ResponseToolUsageWebSearch" + } + } + }, + "model.ResponseToolUsageImageGen": { + "type": "object", + "properties": { + "input_tokens": { + "type": "integer" + }, + "input_tokens_details": { + "$ref": "#/definitions/model.ResponseToolUsageTokensDetails" + }, + "output_tokens": { + "type": "integer" + }, + "output_tokens_details": { + "$ref": "#/definitions/model.ResponseToolUsageTokensDetails" + }, + "total_tokens": { + "type": "integer" + } + } + }, + "model.ResponseToolUsageTokensDetails": { + "type": "object", + "properties": { + "image_tokens": { + "type": "integer" + }, + "text_tokens": { + "type": "integer" + } + } + }, + "model.ResponseToolUsageWebSearch": { + "type": "object", + "properties": { + "num_requests": { + "type": "integer" + } + } + }, "model.ResponseUsage": { "type": "object", "properties": { @@ -13809,9 +14026,15 @@ const docTemplate = `{ "model.ResponseUsageDetails": { "type": "object", "properties": { + "audio_tokens": { + "type": "integer" + }, "cached_tokens": { "type": "integer" }, + "image_tokens": { + "type": "integer" + }, "reasoning_tokens": { "type": "integer" } @@ -13856,6 +14079,12 @@ const docTemplate = `{ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -13943,6 +14172,12 @@ const docTemplate = `{ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -13960,6 +14195,12 @@ const docTemplate = `{ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -14077,6 +14318,12 @@ const docTemplate = `{ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -14291,6 +14538,9 @@ const docTemplate = `{ "audio_input_tokens": { "type": "integer" }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_tokens": { "type": "integer" }, @@ -14315,6 +14565,9 @@ const docTemplate = `{ "total_tokens": { "type": "integer" }, + "video_input_tokens": { + "type": "integer" + }, "web_search_count": { "type": "integer" } diff --git a/core/docs/swagger.json b/core/docs/swagger.json index 8ee0bdf7..84bf535c 100644 --- a/core/docs/swagger.json +++ b/core/docs/swagger.json @@ -1149,7 +1149,7 @@ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1239,7 +1239,7 @@ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1371,7 +1371,7 @@ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1464,7 +1464,7 @@ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1550,7 +1550,7 @@ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -1640,7 +1640,7 @@ }, { "type": "string", - "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", + "description": "Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all", "name": "fields", "in": "query" } @@ -5640,7 +5640,9 @@ 50, 51, 52, - 53 + 53, + 54, + 55 ], "type": "integer", "description": "Channel type", @@ -9526,6 +9528,9 @@ "type": "string" } }, + "skip_tls_verify": { + "type": "boolean" + }, "status": { "type": "integer" }, @@ -9638,6 +9643,9 @@ } } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -9881,6 +9889,9 @@ } } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -10546,6 +10557,9 @@ "format": "float64" } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -10555,6 +10569,9 @@ "override_limit": { "type": "boolean" }, + "override_max_image_generation_count": { + "type": "boolean" + }, "override_price": { "type": "boolean" }, @@ -10640,6 +10657,9 @@ } } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -10731,6 +10751,9 @@ "proxy_url": { "type": "string" }, + "skip_tls_verify": { + "type": "boolean" + }, "type": { "type": "integer" } @@ -10783,6 +10806,9 @@ "proxy_url": { "type": "string" }, + "skip_tls_verify": { + "type": "boolean" + }, "type": { "type": "integer" } @@ -10945,9 +10971,28 @@ "src": { "description": "URI pointing to the icon resource (HTTPS URL or data URI)", "type": "string" + }, + "theme": { + "description": "Theme is an optional specifier for the background theme this icon is designed for.\nUse IconThemeLight for light backgrounds or IconThemeDark for dark backgrounds.", + "allOf": [ + { + "$ref": "#/definitions/mcp.IconTheme" + } + ] } } }, + "mcp.IconTheme": { + "type": "string", + "enum": [ + "light", + "dark" + ], + "x-enum-varnames": [ + "IconThemeLight", + "IconThemeDark" + ] + }, "mcp.Meta": { "type": "object", "properties": { @@ -11035,6 +11080,10 @@ "$ref": "#/definitions/mcp.ToolOutputSchema" } ] + }, + "title": { + "description": "Title is an optional human-readable, UI-friendly display name for the tool.\nIf not provided, clients should use Annotations.Title (if set) and fall back to Name.", + "type": "string" } } }, @@ -11191,6 +11240,9 @@ "audio_input_amount": { "type": "number" }, + "audio_output_amount": { + "type": "number" + }, "cache_creation_amount": { "type": "number" }, @@ -11212,6 +11264,9 @@ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, "web_search_amount": { "type": "number" } @@ -11231,6 +11286,21 @@ } } }, + "model.AsyncUsageStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "x-enum-varnames": [ + "AsyncUsageStatusNone", + "AsyncUsageStatusPending", + "AsyncUsageStatusCompleted", + "AsyncUsageStatusFailed" + ] + }, "model.Channel": { "type": "object", "properties": { @@ -11309,6 +11379,9 @@ "type": "string" } }, + "skip_tls_verify": { + "type": "boolean" + }, "status": { "type": "integer" }, @@ -11422,7 +11495,9 @@ 50, 51, 52, - 53 + 53, + 54, + 55 ], "x-enum-varnames": [ "ChannelTypeOpenAI", @@ -11465,7 +11540,9 @@ "ChannelTypeSangforAICP", "ChannelTypeStreamlake", "ChannelTypeZhipuCoding", - "ChannelTypeFake" + "ChannelTypeFake", + "ChannelTypeAntLing", + "ChannelTypeFakeError" ] }, "model.ChartData": { @@ -11477,6 +11554,12 @@ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -11576,6 +11659,12 @@ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -11611,10 +11700,12 @@ "type": "string", "enum": [ "enabled", + "adaptive", "disabled" ], "x-enum-varnames": [ "ClaudeThinkingTypeEnabled", + "ClaudeThinkingTypeAdaptive", "ClaudeThinkingTypeDisabled" ] }, @@ -11693,6 +11784,9 @@ "prompt_cache_retention": { "type": "string" }, + "reasoning": { + "$ref": "#/definitions/model.ResponseReasoning" + }, "safety_identifier": { "type": "string" }, @@ -11742,6 +11836,12 @@ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -11872,6 +11972,12 @@ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -11925,6 +12031,9 @@ }, "text_tokens": { "type": "integer" + }, + "video_tokens": { + "type": "integer" } } }, @@ -12039,6 +12148,9 @@ "model.GeneralOpenAIRequest": { "type": "object", "properties": { + "enable_thinking": { + "type": "boolean" + }, "frequency_penalty": { "type": "number" }, @@ -12078,6 +12190,9 @@ "prompt_cache_retention": { "type": "string" }, + "reasoning_effort": { + "type": "string" + }, "response_format": { "$ref": "#/definitions/model.ResponseFormat" }, @@ -12108,6 +12223,9 @@ } ] }, + "thinking_budget": { + "type": "integer" + }, "tool_choice": {}, "tools": { "type": "array", @@ -12254,6 +12372,12 @@ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -12390,6 +12514,12 @@ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -12493,6 +12623,9 @@ "format": "float64" } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -12502,6 +12635,9 @@ "override_limit": { "type": "boolean" }, + "override_max_image_generation_count": { + "type": "boolean" + }, "override_price": { "type": "boolean" }, @@ -12810,6 +12946,9 @@ "amount": { "$ref": "#/definitions/model.Amount" }, + "async_usage_status": { + "$ref": "#/definitions/model.AsyncUsageStatus" + }, "channel": { "type": "integer" }, @@ -13005,6 +13144,9 @@ } } }, + "max_image_generation_count": { + "type": "integer" + }, "model": { "type": "string" }, @@ -13087,7 +13229,8 @@ "stepfun", "xai", "doc2x", - "jina" + "jina", + "antgroup" ], "x-enum-varnames": [ "ModelOwnerOpenAI", @@ -13121,7 +13264,8 @@ "ModelOwnerStepFun", "ModelOwnerXAI", "ModelOwnerDoc2x", - "ModelOwnerJina" + "ModelOwnerJina", + "ModelOwnerAntGroup" ] }, "model.Option": { @@ -13214,6 +13358,12 @@ "audio_input_price_unit": { "type": "integer" }, + "audio_output_price": { + "type": "number" + }, + "audio_output_price_unit": { + "type": "integer" + }, "cache_creation_price": { "type": "number" }, @@ -13266,6 +13416,12 @@ "thinking_mode_output_price_unit": { "type": "integer" }, + "video_input_price": { + "type": "number" + }, + "video_input_price_unit": { + "type": "integer" + }, "web_search_price": { "type": "number" }, @@ -13313,6 +13469,9 @@ }, "cached_tokens": { "type": "integer" + }, + "image_tokens": { + "type": "integer" } } }, @@ -13614,6 +13773,9 @@ "model.Response": { "type": "object", "properties": { + "background": { + "type": "boolean" + }, "created_at": { "type": "integer" }, @@ -13676,6 +13838,9 @@ "$ref": "#/definitions/model.ResponseText" }, "tool_choice": {}, + "tool_usage": { + "$ref": "#/definitions/model.ResponseToolUsage" + }, "tools": { "type": "array", "items": { @@ -13733,6 +13898,7 @@ "type": "string", "enum": [ "in_progress", + "queued", "completed", "failed", "incomplete", @@ -13740,6 +13906,7 @@ ], "x-enum-varnames": [ "ResponseStatusInProgress", + "ResponseStatusQueued", "ResponseStatusCompleted", "ResponseStatusFailed", "ResponseStatusIncomplete", @@ -13777,6 +13944,56 @@ } } }, + "model.ResponseToolUsage": { + "type": "object", + "properties": { + "image_gen": { + "$ref": "#/definitions/model.ResponseToolUsageImageGen" + }, + "web_search": { + "$ref": "#/definitions/model.ResponseToolUsageWebSearch" + } + } + }, + "model.ResponseToolUsageImageGen": { + "type": "object", + "properties": { + "input_tokens": { + "type": "integer" + }, + "input_tokens_details": { + "$ref": "#/definitions/model.ResponseToolUsageTokensDetails" + }, + "output_tokens": { + "type": "integer" + }, + "output_tokens_details": { + "$ref": "#/definitions/model.ResponseToolUsageTokensDetails" + }, + "total_tokens": { + "type": "integer" + } + } + }, + "model.ResponseToolUsageTokensDetails": { + "type": "object", + "properties": { + "image_tokens": { + "type": "integer" + }, + "text_tokens": { + "type": "integer" + } + } + }, + "model.ResponseToolUsageWebSearch": { + "type": "object", + "properties": { + "num_requests": { + "type": "integer" + } + } + }, "model.ResponseUsage": { "type": "object", "properties": { @@ -13800,9 +14017,15 @@ "model.ResponseUsageDetails": { "type": "object", "properties": { + "audio_tokens": { + "type": "integer" + }, "cached_tokens": { "type": "integer" }, + "image_tokens": { + "type": "integer" + }, "reasoning_tokens": { "type": "integer" } @@ -13847,6 +14070,12 @@ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -13934,6 +14163,12 @@ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -13951,6 +14186,12 @@ "audio_input_tokens": { "type": "integer" }, + "audio_output_amount": { + "type": "number" + }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_amount": { "type": "number" }, @@ -14068,6 +14309,12 @@ "used_amount": { "type": "number" }, + "video_input_amount": { + "type": "number" + }, + "video_input_tokens": { + "type": "integer" + }, "web_search_amount": { "type": "number" }, @@ -14282,6 +14529,9 @@ "audio_input_tokens": { "type": "integer" }, + "audio_output_tokens": { + "type": "integer" + }, "cache_creation_tokens": { "type": "integer" }, @@ -14306,6 +14556,9 @@ "total_tokens": { "type": "integer" }, + "video_input_tokens": { + "type": "integer" + }, "web_search_count": { "type": "integer" } diff --git a/core/docs/swagger.yaml b/core/docs/swagger.yaml index 163dfcd7..c94b866a 100644 --- a/core/docs/swagger.yaml +++ b/core/docs/swagger.yaml @@ -52,6 +52,8 @@ definitions: items: type: string type: array + skip_tls_verify: + type: boolean status: type: integer type: @@ -127,6 +129,8 @@ definitions: type: object description: map[size]map[quality]price_per_image type: object + max_image_generation_count: + type: integer model: type: string owner: @@ -289,6 +293,8 @@ definitions: type: object description: map[size]map[quality]price_per_image type: object + max_image_generation_count: + type: integer model: type: string owner: @@ -722,12 +728,16 @@ definitions: format: float64 type: number type: object + max_image_generation_count: + type: integer model: type: string override_force_save_detail: type: boolean override_limit: type: boolean + override_max_image_generation_count: + type: boolean override_price: type: boolean override_request_body_storage_max_size: @@ -786,6 +796,8 @@ definitions: type: object description: map[size]map[quality]price_per_image type: object + max_image_generation_count: + type: integer model: type: string owner: @@ -844,6 +856,8 @@ definitions: type: string proxy_url: type: string + skip_tls_verify: + type: boolean type: type: integer required: @@ -878,6 +892,8 @@ definitions: type: string proxy_url: type: string + skip_tls_verify: + type: boolean type: type: integer required: @@ -989,7 +1005,21 @@ definitions: src: description: URI pointing to the icon resource (HTTPS URL or data URI) type: string + theme: + allOf: + - $ref: '#/definitions/mcp.IconTheme' + description: |- + Theme is an optional specifier for the background theme this icon is designed for. + Use IconThemeLight for light backgrounds or IconThemeDark for dark backgrounds. type: object + mcp.IconTheme: + enum: + - light + - dark + type: string + x-enum-varnames: + - IconThemeLight + - IconThemeDark mcp.Meta: properties: additionalFields: @@ -1056,6 +1086,11 @@ definitions: - $ref: '#/definitions/mcp.ToolOutputSchema' description: A JSON Schema object defining the expected output returned by the tool . + title: + description: |- + Title is an optional human-readable, UI-friendly display name for the tool. + If not provided, clients should use Annotations.Title (if set) and fall back to Name. + type: string type: object mcp.ToolAnnotation: properties: @@ -1174,6 +1209,8 @@ definitions: properties: audio_input_amount: type: number + audio_output_amount: + type: number cache_creation_amount: type: number cached_amount: @@ -1188,6 +1225,8 @@ definitions: type: number used_amount: type: number + video_input_amount: + type: number web_search_amount: type: number type: object @@ -1200,6 +1239,18 @@ definitions: model: type: string type: object + model.AsyncUsageStatus: + enum: + - 0 + - 1 + - 2 + - 3 + type: integer + x-enum-varnames: + - AsyncUsageStatusNone + - AsyncUsageStatusPending + - AsyncUsageStatusCompleted + - AsyncUsageStatusFailed model.Channel: properties: balance: @@ -1252,6 +1303,8 @@ definitions: items: type: string type: array + skip_tls_verify: + type: boolean status: type: integer type: @@ -1341,6 +1394,8 @@ definitions: - 51 - 52 - 53 + - 54 + - 55 type: integer x-enum-varnames: - ChannelTypeOpenAI @@ -1384,12 +1439,18 @@ definitions: - ChannelTypeStreamlake - ChannelTypeZhipuCoding - ChannelTypeFake + - ChannelTypeAntLing + - ChannelTypeFakeError model.ChartData: properties: audio_input_amount: type: number audio_input_tokens: type: integer + audio_output_amount: + type: number + audio_output_tokens: + type: integer cache_creation_amount: type: number cache_creation_count: @@ -1456,6 +1517,10 @@ definitions: type: integer used_amount: type: number + video_input_amount: + type: number + video_input_tokens: + type: integer web_search_amount: type: number web_search_count: @@ -1479,10 +1544,12 @@ definitions: model.ClaudeThinkingType: enum: - enabled + - adaptive - disabled type: string x-enum-varnames: - ClaudeThinkingTypeEnabled + - ClaudeThinkingTypeAdaptive - ClaudeThinkingTypeDisabled model.CompletionTokensDetails: properties: @@ -1534,6 +1601,8 @@ definitions: type: string prompt_cache_retention: type: string + reasoning: + $ref: '#/definitions/model.ResponseReasoning' safety_identifier: type: string service_tier: @@ -1567,6 +1636,10 @@ definitions: type: number audio_input_tokens: type: integer + audio_output_amount: + type: number + audio_output_tokens: + type: integer cache_creation_amount: type: number cache_creation_count: @@ -1629,6 +1702,8 @@ definitions: $ref: '#/definitions/model.SummaryDataSet' status_2xx_count: type: integer + status_5xx_count: + type: integer status_400_count: type: integer status_429_count: @@ -1637,8 +1712,6 @@ definitions: type: integer status_500_count: type: integer - status_5xx_count: - type: integer status_other_count: type: integer total_count: @@ -1654,6 +1727,10 @@ definitions: type: integer used_amount: type: number + video_input_amount: + type: number + video_input_tokens: + type: integer web_search_amount: type: number web_search_count: @@ -1689,6 +1766,8 @@ definitions: type: integer text_tokens: type: integer + video_tokens: + type: integer type: object model.EmbeddingRequest: properties: @@ -1765,6 +1844,8 @@ definitions: type: object model.GeneralOpenAIRequest: properties: + enable_thinking: + type: boolean frequency_penalty: type: number function_call: {} @@ -1793,6 +1874,8 @@ definitions: type: string prompt_cache_retention: type: string + reasoning_effort: + type: string response_format: $ref: '#/definitions/model.ResponseFormat' seed: @@ -1812,6 +1895,8 @@ definitions: allOf: - $ref: '#/definitions/model.GeneralThinking' description: aiproxy control field + thinking_budget: + type: integer tool_choice: {} tools: items: @@ -1908,6 +1993,10 @@ definitions: type: number audio_input_tokens: type: integer + audio_output_amount: + type: number + audio_output_tokens: + type: integer cache_creation_amount: type: number cache_creation_count: @@ -1999,6 +2088,10 @@ definitions: type: integer used_amount: type: number + video_input_amount: + type: number + video_input_tokens: + type: integer web_search_amount: type: number web_search_count: @@ -2069,12 +2162,16 @@ definitions: format: float64 type: number type: object + max_image_generation_count: + type: integer model: type: string override_force_save_detail: type: boolean override_limit: type: boolean + override_max_image_generation_count: + type: boolean override_price: type: boolean override_request_body_storage_max_size: @@ -2281,6 +2378,8 @@ definitions: properties: amount: $ref: '#/definitions/model.Amount' + async_usage_status: + $ref: '#/definitions/model.AsyncUsageStatus' channel: type: integer code: @@ -2412,6 +2511,8 @@ definitions: type: object description: map[size]map[quality]price_per_image type: object + max_image_generation_count: + type: integer model: type: string owner: @@ -2478,6 +2579,7 @@ definitions: - xai - doc2x - jina + - antgroup type: string x-enum-varnames: - ModelOwnerOpenAI @@ -2512,6 +2614,7 @@ definitions: - ModelOwnerXAI - ModelOwnerDoc2x - ModelOwnerJina + - ModelOwnerAntGroup model.Option: properties: key: @@ -2572,6 +2675,10 @@ definitions: type: number audio_input_price_unit: type: integer + audio_output_price: + type: number + audio_output_price_unit: + type: integer cache_creation_price: type: number cache_creation_price_unit: @@ -2609,6 +2716,10 @@ definitions: type: number thinking_mode_output_price_unit: type: integer + video_input_price: + type: number + video_input_price_unit: + type: integer web_search_price: type: number web_search_price_unit: @@ -2641,6 +2752,8 @@ definitions: type: integer cached_tokens: type: integer + image_tokens: + type: integer type: object model.ProxyParamType: enum: @@ -2842,6 +2955,8 @@ definitions: type: object model.Response: properties: + background: + type: boolean created_at: type: integer error: @@ -2884,6 +2999,8 @@ definitions: text: $ref: '#/definitions/model.ResponseText' tool_choice: {} + tool_usage: + $ref: '#/definitions/model.ResponseToolUsage' tools: items: $ref: '#/definitions/model.ResponseTool' @@ -2921,6 +3038,7 @@ definitions: model.ResponseStatus: enum: - in_progress + - queued - completed - failed - incomplete @@ -2928,6 +3046,7 @@ definitions: type: string x-enum-varnames: - ResponseStatusInProgress + - ResponseStatusQueued - ResponseStatusCompleted - ResponseStatusFailed - ResponseStatusIncomplete @@ -2952,6 +3071,38 @@ definitions: type: type: string type: object + model.ResponseToolUsage: + properties: + image_gen: + $ref: '#/definitions/model.ResponseToolUsageImageGen' + web_search: + $ref: '#/definitions/model.ResponseToolUsageWebSearch' + type: object + model.ResponseToolUsageImageGen: + properties: + input_tokens: + type: integer + input_tokens_details: + $ref: '#/definitions/model.ResponseToolUsageTokensDetails' + output_tokens: + type: integer + output_tokens_details: + $ref: '#/definitions/model.ResponseToolUsageTokensDetails' + total_tokens: + type: integer + type: object + model.ResponseToolUsageTokensDetails: + properties: + image_tokens: + type: integer + text_tokens: + type: integer + type: object + model.ResponseToolUsageWebSearch: + properties: + num_requests: + type: integer + type: object model.ResponseUsage: properties: input_tokens: @@ -2967,8 +3118,12 @@ definitions: type: object model.ResponseUsageDetails: properties: + audio_tokens: + type: integer cached_tokens: type: integer + image_tokens: + type: integer reasoning_tokens: type: integer type: object @@ -2997,6 +3152,10 @@ definitions: type: number audio_input_tokens: type: integer + audio_output_amount: + type: number + audio_output_tokens: + type: integer cache_creation_amount: type: number cache_creation_count: @@ -3055,6 +3214,10 @@ definitions: type: integer used_amount: type: number + video_input_amount: + type: number + video_input_tokens: + type: integer web_search_amount: type: number web_search_count: @@ -3066,6 +3229,10 @@ definitions: type: number audio_input_tokens: type: integer + audio_output_amount: + type: number + audio_output_tokens: + type: integer cache_creation_amount: type: number cache_creation_count: @@ -3144,6 +3311,10 @@ definitions: type: integer used_amount: type: number + video_input_amount: + type: number + video_input_tokens: + type: integer web_search_amount: type: number web_search_count: @@ -3284,6 +3455,8 @@ definitions: properties: audio_input_tokens: type: integer + audio_output_tokens: + type: integer cache_creation_tokens: type: integer cached_tokens: @@ -3300,6 +3473,8 @@ definitions: type: integer total_tokens: type: integer + video_input_tokens: + type: integer web_search_count: type: integer type: object @@ -4087,7 +4262,7 @@ paths: name: timespan type: string - description: 'Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). - Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. + Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all' in: query name: fields @@ -4145,7 +4320,7 @@ paths: name: timespan type: string - description: 'Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). - Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. + Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all' in: query name: fields @@ -4226,7 +4401,7 @@ paths: name: timespan type: string - description: 'Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). - Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. + Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all' in: query name: fields @@ -4286,7 +4461,7 @@ paths: name: timespan type: string - description: 'Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). - Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. + Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all' in: query name: fields @@ -4342,7 +4517,7 @@ paths: name: timespan type: string - description: 'Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). - Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. + Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all' in: query name: fields @@ -4401,7 +4576,7 @@ paths: name: timespan type: string - description: 'Comma-separated list of fields to select (e.g., request_count,exception_count,cache_hit_count). - Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,output_tokens,image_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. + Available: request_count,retry_count,exception_count,status4xx_count,status5xx_count,status400_count,status429_count,status500_count,cache_hit_count,input_tokens,image_input_tokens,audio_input_tokens,video_input_tokens,output_tokens,image_output_tokens,audio_output_tokens,cached_tokens,cache_creation_tokens,total_tokens,web_search_count,used_amount,total_time,total_ttfb. Groups: count,usage,time,all' in: query name: fields @@ -6863,6 +7038,8 @@ paths: - 51 - 52 - 53 + - 54 + - 55 in: path name: type required: true diff --git a/core/model/async_usage.go b/core/model/async_usage.go index dfad527b..b3b43f7a 100644 --- a/core/model/async_usage.go +++ b/core/model/async_usage.go @@ -232,8 +232,10 @@ func CompleteClaimedAsyncUsageInfo( "InputAmount", "ImageInputAmount", "AudioInputAmount", + "VideoInputAmount", "OutputAmount", "ImageOutputAmount", + "AudioOutputAmount", "CachedAmount", "CacheCreationAmount", "WebSearchAmount", diff --git a/core/model/summary.go b/core/model/summary.go index 1be92767..90a0e870 100644 --- a/core/model/summary.go +++ b/core/model/summary.go @@ -36,8 +36,10 @@ var ( "input_tokens", "image_input_tokens", "audio_input_tokens", + "video_input_tokens", "output_tokens", "image_output_tokens", + "audio_output_tokens", "cached_tokens", "cache_creation_tokens", "reasoning_tokens", @@ -48,8 +50,10 @@ var ( "input_amount", "image_input_amount", "audio_input_amount", + "video_input_amount", "output_amount", "image_output_amount", + "audio_output_amount", "cached_amount", "cache_creation_amount", "web_search_amount", @@ -562,8 +566,10 @@ func appendSummaryUsageUpdateData( {column: "input_tokens", value: int64(usage.InputTokens)}, {column: "image_input_tokens", value: int64(usage.ImageInputTokens)}, {column: "audio_input_tokens", value: int64(usage.AudioInputTokens)}, + {column: "video_input_tokens", value: int64(usage.VideoInputTokens)}, {column: "output_tokens", value: int64(usage.OutputTokens)}, {column: "image_output_tokens", value: int64(usage.ImageOutputTokens)}, + {column: "audio_output_tokens", value: int64(usage.AudioOutputTokens)}, {column: "cached_tokens", value: int64(usage.CachedTokens)}, {column: "cache_creation_tokens", value: int64(usage.CacheCreationTokens)}, {column: "reasoning_tokens", value: int64(usage.ReasoningTokens)}, @@ -596,8 +602,10 @@ func appendSummaryAmountUpdateData( {column: "input_amount", value: amount.InputAmount}, {column: "image_input_amount", value: amount.ImageInputAmount}, {column: "audio_input_amount", value: amount.AudioInputAmount}, + {column: "video_input_amount", value: amount.VideoInputAmount}, {column: "output_amount", value: amount.OutputAmount}, {column: "image_output_amount", value: amount.ImageOutputAmount}, + {column: "audio_output_amount", value: amount.AudioOutputAmount}, {column: "cached_amount", value: amount.CachedAmount}, {column: "cache_creation_amount", value: amount.CacheCreationAmount}, {column: "web_search_amount", value: amount.WebSearchAmount}, diff --git a/core/model/summary_test.go b/core/model/summary_test.go index 65add00a..7f197bcd 100644 --- a/core/model/summary_test.go +++ b/core/model/summary_test.go @@ -850,8 +850,8 @@ func TestParseSummaryFields_AllValidFields(t *testing.T) { "request_count", "retry_count", "exception_count", "status4xx_count", "status5xx_count", "status400_count", "status429_count", "status500_count", "cache_hit_count", - "input_tokens", "image_input_tokens", "audio_input_tokens", - "output_tokens", "image_output_tokens", "cached_tokens", + "input_tokens", "image_input_tokens", "audio_input_tokens", "video_input_tokens", + "output_tokens", "image_output_tokens", "audio_output_tokens", "cached_tokens", "cache_creation_tokens", "total_tokens", "web_search_count", "used_amount", "total_time_milliseconds", "total_ttfb_milliseconds", } diff --git a/core/model/usage.go b/core/model/usage.go index 277becd7..adb0adc3 100644 --- a/core/model/usage.go +++ b/core/model/usage.go @@ -36,12 +36,18 @@ type Price struct { AudioInputPrice ZeroNullFloat64 `json:"audio_input_price,omitempty"` AudioInputPriceUnit ZeroNullInt64 `json:"audio_input_price_unit,omitempty"` + VideoInputPrice ZeroNullFloat64 `json:"video_input_price,omitempty"` + VideoInputPriceUnit ZeroNullInt64 `json:"video_input_price_unit,omitempty"` + OutputPrice ZeroNullFloat64 `json:"output_price,omitempty"` OutputPriceUnit ZeroNullInt64 `json:"output_price_unit,omitempty"` ImageOutputPrice ZeroNullFloat64 `json:"image_output_price,omitempty"` ImageOutputPriceUnit ZeroNullInt64 `json:"image_output_price_unit,omitempty"` + AudioOutputPrice ZeroNullFloat64 `json:"audio_output_price,omitempty"` + AudioOutputPriceUnit ZeroNullInt64 `json:"audio_output_price_unit,omitempty"` + // when ThinkingModeOutputPrice and ReasoningTokens are not 0, OutputPrice and OutputPriceUnit // will be overwritten ThinkingModeOutputPrice ZeroNullFloat64 `json:"thinking_mode_output_price,omitempty"` @@ -360,6 +366,13 @@ func (p *Price) GetAudioInputPriceUnit() int64 { return PriceUnit } +func (p *Price) GetVideoInputPriceUnit() int64 { + if p.VideoInputPriceUnit > 0 { + return int64(p.VideoInputPriceUnit) + } + return PriceUnit +} + func (p *Price) GetOutputPriceUnit() int64 { if p.OutputPriceUnit > 0 { return int64(p.OutputPriceUnit) @@ -374,6 +387,13 @@ func (p *Price) GetImageOutputPriceUnit() int64 { return PriceUnit } +func (p *Price) GetAudioOutputPriceUnit() int64 { + if p.AudioOutputPriceUnit > 0 { + return int64(p.AudioOutputPriceUnit) + } + return PriceUnit +} + func (p *Price) GetCachedPriceUnit() int64 { if p.CachedPriceUnit > 0 { return int64(p.CachedPriceUnit) @@ -399,8 +419,10 @@ type Usage struct { InputTokens ZeroNullInt64 `json:"input_tokens,omitempty"` ImageInputTokens ZeroNullInt64 `json:"image_input_tokens,omitempty"` AudioInputTokens ZeroNullInt64 `json:"audio_input_tokens,omitempty"` + VideoInputTokens ZeroNullInt64 `json:"video_input_tokens,omitempty"` OutputTokens ZeroNullInt64 `json:"output_tokens,omitempty"` ImageOutputTokens ZeroNullInt64 `json:"image_output_tokens,omitempty"` + AudioOutputTokens ZeroNullInt64 `json:"audio_output_tokens,omitempty"` CachedTokens ZeroNullInt64 `json:"cached_tokens,omitempty"` CacheCreationTokens ZeroNullInt64 `json:"cache_creation_tokens,omitempty"` ReasoningTokens ZeroNullInt64 `json:"reasoning_tokens,omitempty"` @@ -412,8 +434,10 @@ func (u *Usage) Add(other Usage) { u.InputTokens += other.InputTokens u.ImageInputTokens += other.ImageInputTokens u.AudioInputTokens += other.AudioInputTokens + u.VideoInputTokens += other.VideoInputTokens u.OutputTokens += other.OutputTokens u.ImageOutputTokens += other.ImageOutputTokens + u.AudioOutputTokens += other.AudioOutputTokens u.CachedTokens += other.CachedTokens u.CacheCreationTokens += other.CacheCreationTokens u.ReasoningTokens += other.ReasoningTokens @@ -425,8 +449,10 @@ type Amount struct { InputAmount float64 `json:"input_amount,omitempty"` ImageInputAmount float64 `json:"image_input_amount,omitempty"` AudioInputAmount float64 `json:"audio_input_amount,omitempty"` + VideoInputAmount float64 `json:"video_input_amount,omitempty"` OutputAmount float64 `json:"output_amount,omitempty"` ImageOutputAmount float64 `json:"image_output_amount,omitempty"` + AudioOutputAmount float64 `json:"audio_output_amount,omitempty"` CachedAmount float64 `json:"cached_amount,omitempty"` CacheCreationAmount float64 `json:"cache_creation_amount,omitempty"` WebSearchAmount float64 `json:"web_search_amount,omitempty"` @@ -443,12 +469,18 @@ func (a *Amount) Add(other Amount) { a.AudioInputAmount = decimal.NewFromFloat(a.AudioInputAmount). Add(decimal.NewFromFloat(other.AudioInputAmount)). InexactFloat64() + a.VideoInputAmount = decimal.NewFromFloat(a.VideoInputAmount). + Add(decimal.NewFromFloat(other.VideoInputAmount)). + InexactFloat64() a.OutputAmount = decimal.NewFromFloat(a.OutputAmount). Add(decimal.NewFromFloat(other.OutputAmount)). InexactFloat64() a.ImageOutputAmount = decimal.NewFromFloat(a.ImageOutputAmount). Add(decimal.NewFromFloat(other.ImageOutputAmount)). InexactFloat64() + a.AudioOutputAmount = decimal.NewFromFloat(a.AudioOutputAmount). + Add(decimal.NewFromFloat(other.AudioOutputAmount)). + InexactFloat64() a.CachedAmount = decimal.NewFromFloat(a.CachedAmount). Add(decimal.NewFromFloat(other.CachedAmount)). InexactFloat64() diff --git a/core/relay/controller/dohelper.go b/core/relay/controller/dohelper.go index 159ad7c6..7189a1cf 100644 --- a/core/relay/controller/dohelper.go +++ b/core/relay/controller/dohelper.go @@ -461,6 +461,10 @@ func updateUsageMetrics(result adaptor.DoResponseResult, log *log.Entry) { log.Data["t_audio_input"] = usage.AudioInputTokens } + if usage.VideoInputTokens > 0 { + log.Data["t_video_input"] = usage.VideoInputTokens + } + if usage.OutputTokens > 0 { log.Data["t_output"] = usage.OutputTokens } @@ -469,6 +473,10 @@ func updateUsageMetrics(result adaptor.DoResponseResult, log *log.Entry) { log.Data["t_image_output"] = usage.ImageOutputTokens } + if usage.AudioOutputTokens > 0 { + log.Data["t_audio_output"] = usage.AudioOutputTokens + } + if usage.TotalTokens > 0 { log.Data["t_total"] = usage.TotalTokens } diff --git a/core/relay/model/chat.go b/core/relay/model/chat.go index b8ac337e..3a96c76a 100644 --- a/core/relay/model/chat.go +++ b/core/relay/model/chat.go @@ -24,11 +24,16 @@ func (u ChatUsage) ToModelUsage() model.Usage { WebSearchCount: model.ZeroNullInt64(u.WebSearchCount), } if u.PromptTokensDetails != nil { + usage.ImageInputTokens = model.ZeroNullInt64(u.PromptTokensDetails.ImageTokens) + usage.AudioInputTokens = model.ZeroNullInt64(u.PromptTokensDetails.AudioTokens) + usage.VideoInputTokens = model.ZeroNullInt64(u.PromptTokensDetails.VideoTokens) usage.CachedTokens = model.ZeroNullInt64(u.PromptTokensDetails.CachedTokens) usage.CacheCreationTokens = model.ZeroNullInt64(u.PromptTokensDetails.CacheCreationTokens) } if u.CompletionTokensDetails != nil { + usage.ImageOutputTokens = model.ZeroNullInt64(u.CompletionTokensDetails.ImageTokens) + usage.AudioOutputTokens = model.ZeroNullInt64(u.CompletionTokensDetails.AudioTokens) usage.ReasoningTokens = model.ZeroNullInt64(u.CompletionTokensDetails.ReasoningTokens) } @@ -51,6 +56,14 @@ func (u *ChatUsage) Add(other *ChatUsage) { u.PromptTokensDetails.Add(other.PromptTokensDetails) } + + if other.CompletionTokensDetails != nil { + if u.CompletionTokensDetails == nil { + u.CompletionTokensDetails = &CompletionTokensDetails{} + } + + u.CompletionTokensDetails.Add(other.CompletionTokensDetails) + } } func (u ChatUsage) ToClaudeUsage() ClaudeUsage { @@ -76,15 +89,27 @@ func (u ChatUsage) ToResponseUsage() ResponseUsage { } if u.PromptTokensDetails != nil && - (u.PromptTokensDetails.CachedTokens > 0 || u.PromptTokensDetails.CacheCreationTokens > 0) { + (u.PromptTokensDetails.CachedTokens > 0 || + u.PromptTokensDetails.CacheCreationTokens > 0 || + u.PromptTokensDetails.ImageTokens > 0 || + u.PromptTokensDetails.VideoTokens > 0 || + u.PromptTokensDetails.AudioTokens > 0) { usage.InputTokensDetails = &ResponseUsageDetails{ + AudioTokens: u.PromptTokensDetails.AudioTokens, CachedTokens: u.PromptTokensDetails.CachedTokens, + ImageTokens: u.PromptTokensDetails.ImageTokens, + VideoTokens: u.PromptTokensDetails.VideoTokens, } } - if u.CompletionTokensDetails != nil && u.CompletionTokensDetails.ReasoningTokens > 0 { + if u.CompletionTokensDetails != nil && + (u.CompletionTokensDetails.ReasoningTokens > 0 || + u.CompletionTokensDetails.AudioTokens > 0 || + u.CompletionTokensDetails.ImageTokens > 0) { usage.OutputTokensDetails = &ResponseUsageDetails{ + AudioTokens: u.CompletionTokensDetails.AudioTokens, ReasoningTokens: u.CompletionTokensDetails.ReasoningTokens, + ImageTokens: u.CompletionTokensDetails.ImageTokens, } } @@ -113,6 +138,8 @@ func (u ChatUsage) ToGeminiUsage() GeminiUsageMetadata { type PromptTokensDetails struct { CachedTokens int64 `json:"cached_tokens"` AudioTokens int64 `json:"audio_tokens"` + ImageTokens int64 `json:"image_tokens,omitempty"` + VideoTokens int64 `json:"video_tokens,omitempty"` CacheCreationTokens int64 `json:"cache_creation_tokens,omitempty"` } @@ -123,6 +150,8 @@ func (d *PromptTokensDetails) Add(other *PromptTokensDetails) { d.CachedTokens += other.CachedTokens d.AudioTokens += other.AudioTokens + d.ImageTokens += other.ImageTokens + d.VideoTokens += other.VideoTokens d.CacheCreationTokens += other.CacheCreationTokens } @@ -134,6 +163,18 @@ type CompletionTokensDetails struct { ImageTokens int64 `json:"image_tokens"` } +func (d *CompletionTokensDetails) Add(other *CompletionTokensDetails) { + if other == nil { + return + } + + d.ReasoningTokens += other.ReasoningTokens + d.AudioTokens += other.AudioTokens + d.AcceptedPredictionTokens += other.AcceptedPredictionTokens + d.RejectedPredictionTokens += other.RejectedPredictionTokens + d.ImageTokens += other.ImageTokens +} + type OpenAIErrorResponse struct { Error OpenAIError `json:"error"` } diff --git a/core/relay/model/embed.go b/core/relay/model/embed.go index 9e75708a..b040c2ac 100644 --- a/core/relay/model/embed.go +++ b/core/relay/model/embed.go @@ -31,6 +31,7 @@ type EmbeddingUsage struct { type EmbeddingPromptTokensDetails struct { TextTokens int64 `json:"text_tokens,omitempty"` ImageTokens int64 `json:"image_tokens,omitempty"` + VideoTokens int64 `json:"video_tokens,omitempty"` } func (u EmbeddingUsage) ToModelUsage() model.Usage { @@ -40,6 +41,7 @@ func (u EmbeddingUsage) ToModelUsage() model.Usage { } if u.PromptTokensDetails != nil { usage.ImageInputTokens = model.ZeroNullInt64(u.PromptTokensDetails.ImageTokens) + usage.VideoInputTokens = model.ZeroNullInt64(u.PromptTokensDetails.VideoTokens) } return usage diff --git a/core/relay/model/gemini.go b/core/relay/model/gemini.go index 39c3e1dc..33e5d1f9 100644 --- a/core/relay/model/gemini.go +++ b/core/relay/model/gemini.go @@ -186,6 +186,39 @@ func (u *GeminiUsageMetadata) GetImageOutputTokens() int64 { return 0 } +// GetAudioInputTokens returns the number of audio input tokens from PromptTokensDetails. +func (u *GeminiUsageMetadata) GetAudioInputTokens() int64 { + for _, detail := range u.PromptTokensDetails { + if detail.Modality == GeminiModalityAudio { + return detail.TokenCount + } + } + + return 0 +} + +// GetVideoInputTokens returns the number of video input tokens from PromptTokensDetails. +func (u *GeminiUsageMetadata) GetVideoInputTokens() int64 { + for _, detail := range u.PromptTokensDetails { + if detail.Modality == GeminiModalityVideo { + return detail.TokenCount + } + } + + return 0 +} + +// GetAudioOutputTokens returns the number of audio output tokens from CandidatesTokensDetails. +func (u *GeminiUsageMetadata) GetAudioOutputTokens() int64 { + for _, detail := range u.CandidatesTokensDetails { + if detail.Modality == GeminiModalityAudio { + return detail.TokenCount + } + } + + return 0 +} + // ToUsage converts GeminiUsageMetadata to ChatUsage format func (u *GeminiUsageMetadata) ToUsage() ChatUsage { chatUsage := ChatUsage{ @@ -194,9 +227,13 @@ func (u *GeminiUsageMetadata) ToUsage() ChatUsage { u.ThoughtsTokenCount, TotalTokens: u.TotalTokenCount, PromptTokensDetails: &PromptTokensDetails{ + AudioTokens: u.GetAudioInputTokens(), CachedTokens: u.CachedContentTokenCount, + ImageTokens: u.GetImageInputTokens(), + VideoTokens: u.GetVideoInputTokens(), }, CompletionTokensDetails: &CompletionTokensDetails{ + AudioTokens: u.GetAudioOutputTokens(), ReasoningTokens: u.ThoughtsTokenCount, ImageTokens: u.GetImageOutputTokens(), }, @@ -213,8 +250,11 @@ func (u *GeminiUsageMetadata) ToModelUsage() model.Usage { usage := model.Usage{ InputTokens: model.ZeroNullInt64(inputTokens), ImageInputTokens: model.ZeroNullInt64(u.GetImageInputTokens()), + AudioInputTokens: model.ZeroNullInt64(u.GetAudioInputTokens()), + VideoInputTokens: model.ZeroNullInt64(u.GetVideoInputTokens()), OutputTokens: model.ZeroNullInt64(u.CandidatesTokenCount + u.ThoughtsTokenCount), ImageOutputTokens: model.ZeroNullInt64(u.GetImageOutputTokens()), + AudioOutputTokens: model.ZeroNullInt64(u.GetAudioOutputTokens()), CachedTokens: model.ZeroNullInt64(u.CachedContentTokenCount), ReasoningTokens: model.ZeroNullInt64(u.ThoughtsTokenCount), TotalTokens: model.ZeroNullInt64(u.TotalTokenCount), @@ -231,14 +271,29 @@ func (u *GeminiUsageMetadata) ToResponseUsage() ResponseUsage { TotalTokens: u.TotalTokenCount, } - if u.CachedContentTokenCount > 0 { + audioInputTokens := u.GetAudioInputTokens() + imageInputTokens := u.GetImageInputTokens() + + videoInputTokens := u.GetVideoInputTokens() + if u.CachedContentTokenCount > 0 || + audioInputTokens > 0 || + imageInputTokens > 0 || + videoInputTokens > 0 { usage.InputTokensDetails = &ResponseUsageDetails{ + AudioTokens: audioInputTokens, CachedTokens: u.CachedContentTokenCount, + ImageTokens: imageInputTokens, + VideoTokens: videoInputTokens, } } - if u.ThoughtsTokenCount > 0 { + audioOutputTokens := u.GetAudioOutputTokens() + + imageOutputTokens := u.GetImageOutputTokens() + if u.ThoughtsTokenCount > 0 || audioOutputTokens > 0 || imageOutputTokens > 0 { usage.OutputTokensDetails = &ResponseUsageDetails{ + AudioTokens: audioOutputTokens, + ImageTokens: imageOutputTokens, ReasoningTokens: u.ThoughtsTokenCount, } } diff --git a/core/relay/model/response.go b/core/relay/model/response.go index fc41ab49..c89c5d76 100644 --- a/core/relay/model/response.go +++ b/core/relay/model/response.go @@ -211,8 +211,11 @@ type InputItem struct { // ResponseUsageDetails represents detailed token usage information type ResponseUsageDetails struct { + AudioTokens int64 `json:"audio_tokens,omitempty"` CachedTokens int64 `json:"cached_tokens,omitempty"` ReasoningTokens int64 `json:"reasoning_tokens,omitempty"` + ImageTokens int64 `json:"image_tokens,omitempty"` + VideoTokens int64 `json:"video_tokens,omitempty"` } // ResponseUsage represents usage information for a response @@ -383,10 +386,15 @@ func (u *ResponseUsage) ToModelUsage() model.Usage { } if u.InputTokensDetails != nil { + usage.ImageInputTokens = model.ZeroNullInt64(u.InputTokensDetails.ImageTokens) + usage.AudioInputTokens = model.ZeroNullInt64(u.InputTokensDetails.AudioTokens) + usage.VideoInputTokens = model.ZeroNullInt64(u.InputTokensDetails.VideoTokens) usage.CachedTokens = model.ZeroNullInt64(u.InputTokensDetails.CachedTokens) } if u.OutputTokensDetails != nil { + usage.ImageOutputTokens = model.ZeroNullInt64(u.OutputTokensDetails.ImageTokens) + usage.AudioOutputTokens = model.ZeroNullInt64(u.OutputTokensDetails.AudioTokens) usage.ReasoningTokens = model.ZeroNullInt64(u.OutputTokensDetails.ReasoningTokens) } @@ -401,15 +409,27 @@ func (u *ResponseUsage) ToChatUsage() ChatUsage { TotalTokens: u.TotalTokens, } - if u.InputTokensDetails != nil && u.InputTokensDetails.CachedTokens > 0 { + if u.InputTokensDetails != nil && + (u.InputTokensDetails.CachedTokens > 0 || + u.InputTokensDetails.AudioTokens > 0 || + u.InputTokensDetails.ImageTokens > 0 || + u.InputTokensDetails.VideoTokens > 0) { usage.PromptTokensDetails = &PromptTokensDetails{ + AudioTokens: u.InputTokensDetails.AudioTokens, CachedTokens: u.InputTokensDetails.CachedTokens, + ImageTokens: u.InputTokensDetails.ImageTokens, + VideoTokens: u.InputTokensDetails.VideoTokens, } } - if u.OutputTokensDetails != nil && u.OutputTokensDetails.ReasoningTokens > 0 { + if u.OutputTokensDetails != nil && + (u.OutputTokensDetails.ReasoningTokens > 0 || + u.OutputTokensDetails.AudioTokens > 0 || + u.OutputTokensDetails.ImageTokens > 0) { usage.CompletionTokensDetails = &CompletionTokensDetails{ + AudioTokens: u.OutputTokensDetails.AudioTokens, ReasoningTokens: u.OutputTokensDetails.ReasoningTokens, + ImageTokens: u.OutputTokensDetails.ImageTokens, } } diff --git a/core/relay/model/usage_conversion_test.go b/core/relay/model/usage_conversion_test.go index de0f9dde..52c02364 100644 --- a/core/relay/model/usage_conversion_test.go +++ b/core/relay/model/usage_conversion_test.go @@ -14,10 +14,15 @@ func TestChatUsageConversions(t *testing.T) { CompletionTokens: 50, TotalTokens: 150, PromptTokensDetails: &model.PromptTokensDetails{ + AudioTokens: 5, + ImageTokens: 6, + VideoTokens: 9, CachedTokens: 20, CacheCreationTokens: 10, }, CompletionTokensDetails: &model.CompletionTokensDetails{ + AudioTokens: 7, + ImageTokens: 8, ReasoningTokens: 30, }, } @@ -28,11 +33,27 @@ func TestChatUsageConversions(t *testing.T) { assert.Equal(t, int64(50), responseUsage.OutputTokens) assert.Equal(t, int64(150), responseUsage.TotalTokens) assert.NotNil(t, responseUsage.InputTokensDetails) + assert.Equal(t, int64(5), responseUsage.InputTokensDetails.AudioTokens) + assert.Equal(t, int64(6), responseUsage.InputTokensDetails.ImageTokens) + assert.Equal(t, int64(9), responseUsage.InputTokensDetails.VideoTokens) assert.Equal(t, int64(20), responseUsage.InputTokensDetails.CachedTokens) assert.NotNil(t, responseUsage.OutputTokensDetails) + assert.Equal(t, int64(7), responseUsage.OutputTokensDetails.AudioTokens) + assert.Equal(t, int64(8), responseUsage.OutputTokensDetails.ImageTokens) assert.Equal(t, int64(30), responseUsage.OutputTokensDetails.ReasoningTokens) }) + t.Run("ChatUsage to model Usage", func(t *testing.T) { + modelUsage := chatUsage.ToModelUsage() + assert.Equal(t, coremodel.ZeroNullInt64(100), modelUsage.InputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(6), modelUsage.ImageInputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(5), modelUsage.AudioInputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(9), modelUsage.VideoInputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(50), modelUsage.OutputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(8), modelUsage.ImageOutputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(7), modelUsage.AudioOutputTokens) + }) + t.Run("ChatUsage to ClaudeUsage", func(t *testing.T) { claudeUsage := chatUsage.ToClaudeUsage() assert.Equal(t, int64(100), claudeUsage.InputTokens) @@ -57,9 +78,14 @@ func TestResponseUsageConversions(t *testing.T) { OutputTokens: 50, TotalTokens: 150, InputTokensDetails: &model.ResponseUsageDetails{ + AudioTokens: 5, + ImageTokens: 6, + VideoTokens: 9, CachedTokens: 20, }, OutputTokensDetails: &model.ResponseUsageDetails{ + AudioTokens: 7, + ImageTokens: 8, ReasoningTokens: 30, }, } @@ -70,11 +96,27 @@ func TestResponseUsageConversions(t *testing.T) { assert.Equal(t, int64(50), chatUsage.CompletionTokens) assert.Equal(t, int64(150), chatUsage.TotalTokens) assert.NotNil(t, chatUsage.PromptTokensDetails) + assert.Equal(t, int64(5), chatUsage.PromptTokensDetails.AudioTokens) + assert.Equal(t, int64(6), chatUsage.PromptTokensDetails.ImageTokens) + assert.Equal(t, int64(9), chatUsage.PromptTokensDetails.VideoTokens) assert.Equal(t, int64(20), chatUsage.PromptTokensDetails.CachedTokens) assert.NotNil(t, chatUsage.CompletionTokensDetails) + assert.Equal(t, int64(7), chatUsage.CompletionTokensDetails.AudioTokens) + assert.Equal(t, int64(8), chatUsage.CompletionTokensDetails.ImageTokens) assert.Equal(t, int64(30), chatUsage.CompletionTokensDetails.ReasoningTokens) }) + t.Run("ResponseUsage to model Usage", func(t *testing.T) { + modelUsage := responseUsage.ToModelUsage() + assert.Equal(t, coremodel.ZeroNullInt64(100), modelUsage.InputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(6), modelUsage.ImageInputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(5), modelUsage.AudioInputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(9), modelUsage.VideoInputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(50), modelUsage.OutputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(8), modelUsage.ImageOutputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(7), modelUsage.AudioOutputTokens) + }) + t.Run("ResponseUsage to ClaudeUsage", func(t *testing.T) { claudeUsage := responseUsage.ToClaudeUsage() assert.Equal(t, int64(100), claudeUsage.InputTokens) @@ -135,6 +177,15 @@ func TestGeminiUsageConversions(t *testing.T) { TotalTokenCount: 150, ThoughtsTokenCount: 30, CachedContentTokenCount: 20, + PromptTokensDetails: []model.GeminiTokensDetail{ + {Modality: model.GeminiModalityImage, TokenCount: 6}, + {Modality: model.GeminiModalityAudio, TokenCount: 5}, + {Modality: model.GeminiModalityVideo, TokenCount: 9}, + }, + CandidatesTokensDetails: []model.GeminiTokensDetail{ + {Modality: model.GeminiModalityImage, TokenCount: 8}, + {Modality: model.GeminiModalityAudio, TokenCount: 7}, + }, } t.Run("GeminiUsage to ChatUsage", func(t *testing.T) { @@ -143,8 +194,13 @@ func TestGeminiUsageConversions(t *testing.T) { assert.Equal(t, int64(80), chatUsage.CompletionTokens) // 50 + 30 assert.Equal(t, int64(150), chatUsage.TotalTokens) assert.NotNil(t, chatUsage.PromptTokensDetails) + assert.Equal(t, int64(5), chatUsage.PromptTokensDetails.AudioTokens) + assert.Equal(t, int64(6), chatUsage.PromptTokensDetails.ImageTokens) + assert.Equal(t, int64(9), chatUsage.PromptTokensDetails.VideoTokens) assert.Equal(t, int64(20), chatUsage.PromptTokensDetails.CachedTokens) assert.NotNil(t, chatUsage.CompletionTokensDetails) + assert.Equal(t, int64(7), chatUsage.CompletionTokensDetails.AudioTokens) + assert.Equal(t, int64(8), chatUsage.CompletionTokensDetails.ImageTokens) assert.Equal(t, int64(30), chatUsage.CompletionTokensDetails.ReasoningTokens) }) @@ -154,11 +210,32 @@ func TestGeminiUsageConversions(t *testing.T) { assert.Equal(t, int64(50), responseUsage.OutputTokens) assert.Equal(t, int64(150), responseUsage.TotalTokens) assert.NotNil(t, responseUsage.InputTokensDetails) + assert.Equal(t, int64(5), responseUsage.InputTokensDetails.AudioTokens) + assert.Equal(t, int64(6), responseUsage.InputTokensDetails.ImageTokens) + assert.Equal(t, int64(9), responseUsage.InputTokensDetails.VideoTokens) assert.Equal(t, int64(20), responseUsage.InputTokensDetails.CachedTokens) assert.NotNil(t, responseUsage.OutputTokensDetails) + assert.Equal(t, int64(7), responseUsage.OutputTokensDetails.AudioTokens) + assert.Equal(t, int64(8), responseUsage.OutputTokensDetails.ImageTokens) assert.Equal(t, int64(30), responseUsage.OutputTokensDetails.ReasoningTokens) }) + t.Run( + "GeminiUsage to ChatUsage to model Usage preserves multimodal input details", + func(t *testing.T) { + modelUsage := geminiUsage.ToUsage().ToModelUsage() + assert.Equal(t, coremodel.ZeroNullInt64(100), modelUsage.InputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(6), modelUsage.ImageInputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(5), modelUsage.AudioInputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(9), modelUsage.VideoInputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(80), modelUsage.OutputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(8), modelUsage.ImageOutputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(7), modelUsage.AudioOutputTokens) + assert.Equal(t, coremodel.ZeroNullInt64(30), modelUsage.ReasoningTokens) + assert.Equal(t, coremodel.ZeroNullInt64(150), modelUsage.TotalTokens) + }, + ) + t.Run("GeminiUsage to ClaudeUsage", func(t *testing.T) { claudeUsage := geminiUsage.ToClaudeUsage() assert.Equal(t, int64(100), claudeUsage.InputTokens) diff --git a/core/relay/plugin/streamfake/fake.go b/core/relay/plugin/streamfake/fake.go index 5ffe6451..18716d11 100644 --- a/core/relay/plugin/streamfake/fake.go +++ b/core/relay/plugin/streamfake/fake.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "maps" "net/http" "slices" "strconv" @@ -190,6 +191,8 @@ type fakeStreamResponseWriter struct { toolCalls []*relaymodel.ToolCall contentParts []relaymodel.MessageContent // for image/multimodal content signature string // for thought signature + audio map[string]*bytes.Buffer + audioFields map[string]any // Azure OpenAI content filtering fields promptFilterResults *ast.Node // prompt-level filter results (from first chunk) @@ -306,6 +309,8 @@ func (rw *fakeStreamResponseWriter) parseStreamingData(data []byte) error { rw.signature = signature } + rw.processAudioDelta(deltaNode.Get("audio")) + _ = deltaNode.Get("tool_calls"). ForEach(func(_ ast.Sequence, toolCallNode *ast.Node) bool { if toolCallNode == nil || toolCallNode.TypeSafe() == ast.V_NULL { @@ -390,6 +395,10 @@ func (rw *fakeStreamResponseWriter) convertToNonStream() ([]byte, error) { message["signature"] = rw.signature } + if audio := rw.buildAudio(); len(audio) > 0 { + message["audio"] = audio + } + if len(rw.toolCalls) > 0 { message["tool_calls"] = rw.buildToolCalls() } @@ -439,6 +448,74 @@ func (rw *fakeStreamResponseWriter) convertToNonStream() ([]byte, error) { return lastChunk.MarshalJSON() } +func (rw *fakeStreamResponseWriter) processAudioDelta(audioNode *ast.Node) { + if audioNode == nil || audioNode.TypeSafe() != ast.V_OBJECT { + return + } + + if err := audioNode.Check(); err != nil { + return + } + + _ = audioNode.ForEach(func(seq ast.Sequence, fieldNode *ast.Node) bool { + if seq.Key == nil || fieldNode == nil { + return true + } + + key := *seq.Key + + if shouldAppendAudioField(key) && fieldNode.TypeSafe() == ast.V_STRING { + value, err := fieldNode.String() + if err != nil { + return true + } + + if rw.audio == nil { + rw.audio = make(map[string]*bytes.Buffer) + } + + builder := rw.audio[key] + if builder == nil { + builder = &bytes.Buffer{} + rw.audio[key] = builder + } + + builder.WriteString(value) + + return true + } + + value, err := fieldNode.Interface() + if err != nil { + return true + } + + if rw.audioFields == nil { + rw.audioFields = make(map[string]any) + } + + rw.audioFields[key] = value + + return true + }) +} + +func shouldAppendAudioField(key string) bool { + return key == "data" || key == "transcript" +} + +func (rw *fakeStreamResponseWriter) buildAudio() map[string]any { + audio := make(map[string]any, len(rw.audio)+len(rw.audioFields)) + + maps.Copy(audio, rw.audioFields) + + for key, builder := range rw.audio { + audio[key] = builder.String() + } + + return audio +} + func (rw *fakeStreamResponseWriter) buildToolCalls() []*relaymodel.ToolCall { if len(rw.toolCalls) == 0 { return nil diff --git a/core/relay/plugin/streamfake/fake_test.go b/core/relay/plugin/streamfake/fake_test.go index 3cc38592..67e97614 100644 --- a/core/relay/plugin/streamfake/fake_test.go +++ b/core/relay/plugin/streamfake/fake_test.go @@ -218,6 +218,48 @@ func TestConvertToNonStreamWithAllFields(t *testing.T) { assert.Equal(t, float64(101), usage["total_tokens"]) } +func TestParseStreamingDataWithAudioDelta(t *testing.T) { + rw := &fakeStreamResponseWriter{} + + chunks := []string{ + `{"choices":[{"delta":{"role":"assistant","audio":{"id":"audio-","data":"AAAA","transcript":"hel","expires_at":100}},"finish_reason":null,"index":0}],"created":1767597874,"id":"chatcmpl-test","model":"gpt-4o-audio-preview","object":"chat.completion.chunk"}`, + `{"choices":[{"delta":{"audio":{"id":"test","data":"BBBB","transcript":"lo","expires_at":200}},"finish_reason":null,"index":0}],"created":1767597874,"id":"chatcmpl-test","model":"gpt-4o-audio-preview","object":"chat.completion.chunk"}`, + `{"choices":[{"delta":{},"finish_reason":"stop","index":0}],"created":1767597874,"id":"chatcmpl-test","model":"gpt-4o-audio-preview","object":"chat.completion.chunk","usage":{"completion_tokens":10,"completion_tokens_details":{"audio_tokens":8},"prompt_tokens":2,"total_tokens":12}}`, + } + + for _, chunk := range chunks { + err := rw.parseStreamingData([]byte(chunk)) + require.NoError(t, err) + } + + result, err := rw.convertToNonStream() + require.NoError(t, err) + + var response map[string]any + + err = sonic.Unmarshal(result, &response) + require.NoError(t, err) + + choices, ok := response["choices"].([]any) + require.True(t, ok) + require.Len(t, choices, 1) + + choice, ok := choices[0].(map[string]any) + require.True(t, ok) + + message, ok := choice["message"].(map[string]any) + require.True(t, ok) + + audio, ok := message["audio"].(map[string]any) + require.True(t, ok) + + assert.Equal(t, "test", audio["id"]) + assert.Equal(t, "AAAABBBB", audio["data"]) + assert.Equal(t, "hello", audio["transcript"]) + assert.Equal(t, float64(200), audio["expires_at"]) + assert.Equal(t, "stop", choice["finish_reason"]) +} + func TestParseStreamingDataIgnoresSSEData(t *testing.T) { rw := &fakeStreamResponseWriter{} From bd8eb0912c73576ef2ce47c18a71ef843838460f Mon Sep 17 00:00:00 2001 From: zijiren233 Date: Wed, 20 May 2026 21:17:25 +0800 Subject: [PATCH 2/2] feat: add more tokens --- web/public/locales/en/translation.json | 18 +++ web/public/locales/zh/translation.json | 18 +++ web/src/components/price/PriceDisplay.tsx | 4 + web/src/components/price/PriceFormFields.tsx | 16 +++ .../log/components/ExpandedLogContent.tsx | 10 ++ .../feature/model/components/ModelForm.tsx | 4 + .../monitor/components/MonitorCharts.tsx | 4 + web/src/feature/monitor/hooks.ts | 116 ++++++++++++++---- web/src/types/dashboard.ts | 12 ++ web/src/types/log.ts | 14 +++ web/src/types/model.ts | 4 + web/src/validation/model.ts | 8 ++ 12 files changed, 202 insertions(+), 26 deletions(-) diff --git a/web/public/locales/en/translation.json b/web/public/locales/en/translation.json index a70dc5f8..dd894252 100644 --- a/web/public/locales/en/translation.json +++ b/web/public/locales/en/translation.json @@ -154,8 +154,10 @@ "textInput": "Text Input", "imageInput": "Image Input", "audioInput": "Audio Input", + "videoInput": "Video Input", "textOutput": "Text Output", "imageOutput": "Image Output", + "audioOutput": "Audio Output", "reasoning": "Reasoning", "cached": "Cached", "cacheCreation": "Cache Creation" @@ -184,8 +186,10 @@ "cacheCreation": "Cache Creation", "imageInput": "Image Input", "audioInput": "Audio Input", + "videoInput": "Video Input", "textOutput": "Text Output", "imageOutput": "Image Output", + "audioOutput": "Audio Output", "webSearch": "Web Search" }, "avgResponseTime": "Avg Response Time", @@ -699,6 +703,10 @@ "cacheCreation": "Cache Creation", "cached": "Cached", "imageInput": "Image Input", + "audioInput": "Audio Input", + "videoInput": "Video Input", + "imageOutput": "Image Output", + "audioOutput": "Audio Output", "reasoning": "Reasoning", "total": "Total", "webSearchCount": "Web Search Count", @@ -712,6 +720,10 @@ "cacheCreationPrice": "Cache Creation Price", "cachedPrice": "Cached Price", "imageInputPrice": "Image Input Price", + "audioInputPrice": "Audio Input Price", + "videoInputPrice": "Video Input Price", + "imageOutputPrice": "Image Output Price", + "audioOutputPrice": "Audio Output Price", "perRequestPrice": "Per Request Price", "thinkingPrice": "Thinking Price", "webSearchPrice": "Web Search Price", @@ -721,8 +733,10 @@ "cacheCreation": "Cache Creation Cost", "imageInput": "Image Input Cost", "audioInput": "Audio Input Cost", + "videoInput": "Video Input Cost", "output": "Text Output Cost", "imageOutput": "Image Output Cost", + "audioOutput": "Audio Output Cost", "webSearch": "Web Search Cost" }, "filters": { @@ -1097,6 +1111,10 @@ "imageOutputPriceUnit": "Unit (tokens)", "audioInputPrice": "Audio Input Price", "audioInputPriceUnit": "Unit (tokens)", + "videoInputPrice": "Video Input Price", + "videoInputPriceUnit": "Unit (tokens)", + "audioOutputPrice": "Audio Output Price", + "audioOutputPriceUnit": "Unit (tokens)", "thinkingOutputPrice": "Thinking Output Price", "thinkingOutputPriceUnit": "Unit (tokens)", "webSearchPrice": "Web Search Price", diff --git a/web/public/locales/zh/translation.json b/web/public/locales/zh/translation.json index d7c565fb..92a34adf 100644 --- a/web/public/locales/zh/translation.json +++ b/web/public/locales/zh/translation.json @@ -166,8 +166,10 @@ "cacheCreation": "缓存创建", "imageInput": "图片输入", "audioInput": "音频输入", + "videoInput": "视频输入", "textOutput": "文本输出", "imageOutput": "图片输出", + "audioOutput": "音频输出", "webSearch": "联网搜索" }, "avgResponseTime": "平均耗时时间", @@ -197,8 +199,10 @@ "textInput": "文本输入", "imageInput": "图片输入", "audioInput": "音频输入", + "videoInput": "视频输入", "textOutput": "文本输出", "imageOutput": "图片输出", + "audioOutput": "音频输出", "reasoning": "推理", "cached": "缓存命中", "cacheCreation": "缓存创建" @@ -687,6 +691,10 @@ "cacheCreation": "缓存创建", "cached": "缓存", "imageInput": "图片输入", + "audioInput": "音频输入", + "videoInput": "视频输入", + "imageOutput": "图片输出", + "audioOutput": "音频输出", "reasoning": "推理", "total": "总计", "webSearchCount": "网络搜索次数", @@ -700,6 +708,10 @@ "cacheCreationPrice": "缓存创建价格", "cachedPrice": "缓存价格", "imageInputPrice": "图片输入价格", + "audioInputPrice": "音频输入价格", + "videoInputPrice": "视频输入价格", + "imageOutputPrice": "图片输出价格", + "audioOutputPrice": "音频输出价格", "perRequestPrice": "每次请求价格", "thinkingPrice": "思考价格", "webSearchPrice": "搜索价格", @@ -709,8 +721,10 @@ "cacheCreation": "缓存创建消费", "imageInput": "图片输入消费", "audioInput": "音频输入消费", + "videoInput": "视频输入消费", "output": "文本输出消费", "imageOutput": "图片输出消费", + "audioOutput": "音频输出消费", "webSearch": "联网搜索消费" }, "filters": { @@ -1085,6 +1099,10 @@ "imageOutputPriceUnit": "单位 (tokens)", "audioInputPrice": "音频输入价格", "audioInputPriceUnit": "单位 (tokens)", + "videoInputPrice": "视频输入价格", + "videoInputPriceUnit": "单位 (tokens)", + "audioOutputPrice": "音频输出价格", + "audioOutputPriceUnit": "单位 (tokens)", "thinkingOutputPrice": "思考输出价格", "thinkingOutputPriceUnit": "单位 (tokens)", "webSearchPrice": "搜索价格", diff --git a/web/src/components/price/PriceDisplay.tsx b/web/src/components/price/PriceDisplay.tsx index e3ea1595..233f041a 100644 --- a/web/src/components/price/PriceDisplay.tsx +++ b/web/src/components/price/PriceDisplay.tsx @@ -49,6 +49,8 @@ export function PriceDisplay({ price }: PriceDisplayProps) { { label: t('group.price.imageInputPrice'), value: formatPriceValue(target.image_input_price, target.image_input_price_unit) }, { label: t('group.price.imageOutputPrice'), value: formatPriceValue(target.image_output_price, target.image_output_price_unit) }, { label: t('group.price.audioInputPrice'), value: formatPriceValue(target.audio_input_price, target.audio_input_price_unit) }, + { label: t('group.price.videoInputPrice'), value: formatPriceValue(target.video_input_price, target.video_input_price_unit) }, + { label: t('group.price.audioOutputPrice'), value: formatPriceValue(target.audio_output_price, target.audio_output_price_unit) }, { label: t('group.price.thinkingOutputPrice'), value: formatPriceValue(target.thinking_mode_output_price, target.thinking_mode_output_price_unit) }, { label: t('group.price.webSearchPrice'), value: formatPriceValue(target.web_search_price, target.web_search_price_unit) }, ].filter((row) => row.value !== null) @@ -93,6 +95,8 @@ export function PriceDisplay({ price }: PriceDisplayProps) { formatPriceValue(price.image_input_price, price.image_input_price_unit) && `Img In: ${formatPriceValue(price.image_input_price, price.image_input_price_unit)}`, formatPriceValue(price.image_output_price, price.image_output_price_unit) && `Img Out: ${formatPriceValue(price.image_output_price, price.image_output_price_unit)}`, formatPriceValue(price.audio_input_price, price.audio_input_price_unit) && `Audio In: ${formatPriceValue(price.audio_input_price, price.audio_input_price_unit)}`, + formatPriceValue(price.video_input_price, price.video_input_price_unit) && `Video In: ${formatPriceValue(price.video_input_price, price.video_input_price_unit)}`, + formatPriceValue(price.audio_output_price, price.audio_output_price_unit) && `Audio Out: ${formatPriceValue(price.audio_output_price, price.audio_output_price_unit)}`, formatPriceValue(price.thinking_mode_output_price, price.thinking_mode_output_price_unit) && `Think Out: ${formatPriceValue(price.thinking_mode_output_price, price.thinking_mode_output_price_unit)}`, formatPriceValue(price.web_search_price, price.web_search_price_unit) && `Search: ${formatPriceValue(price.web_search_price, price.web_search_price_unit)}`, ].filter(Boolean) diff --git a/web/src/components/price/PriceFormFields.tsx b/web/src/components/price/PriceFormFields.tsx index 6138b633..1c020cff 100644 --- a/web/src/components/price/PriceFormFields.tsx +++ b/web/src/components/price/PriceFormFields.tsx @@ -203,6 +203,22 @@ function BasePriceFields({ price, onChange }: { price: ModelPrice; onChange: (pr onValueChange={(v) => updateField('audio_input_price', v)} onUnitChange={(v) => updateField('audio_input_price_unit', v)} /> + updateField('video_input_price', v)} + onUnitChange={(v) => updateField('video_input_price_unit', v)} + /> + updateField('audio_output_price', v)} + onUnitChange={(v) => updateField('audio_output_price_unit', v)} + /> {
{t('log.cacheCreation')}: {log.usage?.cache_creation_tokens?.toLocaleString() || 0}
{t('log.cached')}: {log.usage?.cached_tokens?.toLocaleString() || 0}
{t('log.imageInput')}: {log.usage?.image_input_tokens?.toLocaleString() || 0}
+
{t('log.audioInput')}: {log.usage?.audio_input_tokens?.toLocaleString() || 0}
+
{t('log.videoInput')}: {log.usage?.video_input_tokens?.toLocaleString() || 0}
+
{t('log.imageOutput')}: {log.usage?.image_output_tokens?.toLocaleString() || 0}
+
{t('log.audioOutput')}: {log.usage?.audio_output_tokens?.toLocaleString() || 0}
{t('log.reasoning')}: {log.usage?.reasoning_tokens?.toLocaleString() || 0}
{t('log.webSearchCount')}: {log.usage?.web_search_count || 0}
@@ -227,6 +231,10 @@ export const ExpandedLogContent = ({ log }: { log: LogRecord }) => {
{t('log.cacheCreationPrice')}: {formatPrice(log.price?.cache_creation_price, log.price?.cache_creation_price_unit)}
{t('log.cachedPrice')}: {formatPrice(log.price?.cached_price, log.price?.cached_price_unit)}
{t('log.imageInputPrice')}: {formatPrice(log.price?.image_input_price, log.price?.image_input_price_unit)}
+
{t('log.audioInputPrice')}: {formatPrice(log.price?.audio_input_price, log.price?.audio_input_price_unit)}
+
{t('log.videoInputPrice')}: {formatPrice(log.price?.video_input_price, log.price?.video_input_price_unit)}
+
{t('log.imageOutputPrice')}: {formatPrice(log.price?.image_output_price, log.price?.image_output_price_unit)}
+
{t('log.audioOutputPrice')}: {formatPrice(log.price?.audio_output_price, log.price?.audio_output_price_unit)}
{t('log.perRequestPrice')}: {log.price?.per_request_price || '-'}
{t('log.thinkingPrice')}: {formatPrice(log.price?.thinking_mode_output_price, log.price?.thinking_mode_output_price_unit)}
{t('log.webSearchPrice')}: {formatPrice(log.price?.web_search_price, log.price?.web_search_price_unit)}
@@ -243,8 +251,10 @@ export const ExpandedLogContent = ({ log }: { log: LogRecord }) => {
{t('log.costBreakdown.cacheCreation')}: {formatAmount(amount?.cache_creation_amount)}
{t('log.costBreakdown.imageInput')}: {formatAmount(amount?.image_input_amount)}
{t('log.costBreakdown.audioInput')}: {formatAmount(amount?.audio_input_amount)}
+
{t('log.costBreakdown.videoInput')}: {formatAmount(amount?.video_input_amount)}
{t('log.costBreakdown.output')}: {formatAmount(amount?.output_amount)}
{t('log.costBreakdown.imageOutput')}: {formatAmount(amount?.image_output_amount)}
+
{t('log.costBreakdown.audioOutput')}: {formatAmount(amount?.audio_output_amount)}
{t('log.costBreakdown.webSearch')}: {formatAmount(amount?.web_search_amount)}
diff --git a/web/src/feature/model/components/ModelForm.tsx b/web/src/feature/model/components/ModelForm.tsx index 933aca37..1e578a04 100644 --- a/web/src/feature/model/components/ModelForm.tsx +++ b/web/src/feature/model/components/ModelForm.tsx @@ -63,6 +63,10 @@ const KNOWN_PRICE_KEYS = new Set([ 'image_output_price_unit', 'audio_input_price', 'audio_input_price_unit', + 'video_input_price', + 'video_input_price_unit', + 'audio_output_price', + 'audio_output_price_unit', 'thinking_mode_output_price', 'thinking_mode_output_price_unit', 'web_search_price', diff --git a/web/src/feature/monitor/components/MonitorCharts.tsx b/web/src/feature/monitor/components/MonitorCharts.tsx index b3c3effd..c4a09804 100644 --- a/web/src/feature/monitor/components/MonitorCharts.tsx +++ b/web/src/feature/monitor/components/MonitorCharts.tsx @@ -562,8 +562,10 @@ export function MonitorCharts({ chartData, modelRanking, detailRanking = [], has { key: 'cacheCreationTokens', name: t('monitor.charts.tokensBreakdown.cacheCreation'), color: '#a78bfa' }, { key: 'imageInputTokens', name: t('monitor.charts.tokensBreakdown.imageInput'), color: '#06b6d4' }, { key: 'audioInputTokens', name: t('monitor.charts.tokensBreakdown.audioInput'), color: '#8b5cf6' }, + { key: 'videoInputTokens', name: t('monitor.charts.tokensBreakdown.videoInput'), color: '#ec4899' }, { key: 'textOutputTokens', name: t('monitor.charts.tokensBreakdown.textOutput'), color: '#10b981' }, { key: 'imageOutputTokens', name: t('monitor.charts.tokensBreakdown.imageOutput'), color: '#14b8a6' }, + { key: 'audioOutputTokens', name: t('monitor.charts.tokensBreakdown.audioOutput'), color: '#f97316' }, ] // Filter out series that have no data at all const activeSeries = tokenSeries.filter(s => @@ -661,8 +663,10 @@ export function MonitorCharts({ chartData, modelRanking, detailRanking = [], has { key: 'cacheCreationAmount', name: t('monitor.charts.costBreakdownTypes.cacheCreation'), color: '#a78bfa' }, { key: 'imageInputAmount', name: t('monitor.charts.costBreakdownTypes.imageInput'), color: '#06b6d4' }, { key: 'audioInputAmount', name: t('monitor.charts.costBreakdownTypes.audioInput'), color: '#8b5cf6' }, + { key: 'videoInputAmount', name: t('monitor.charts.costBreakdownTypes.videoInput'), color: '#ec4899' }, { key: 'outputAmount', name: t('monitor.charts.costBreakdownTypes.textOutput'), color: '#10b981' }, { key: 'imageOutputAmount', name: t('monitor.charts.costBreakdownTypes.imageOutput'), color: '#14b8a6' }, + { key: 'audioOutputAmount', name: t('monitor.charts.costBreakdownTypes.audioOutput'), color: '#f97316' }, { key: 'webSearchAmount', name: t('monitor.charts.costBreakdownTypes.webSearch'), color: '#0ea5e9' }, ] // Filter out series that have no data at all diff --git a/web/src/feature/monitor/hooks.ts b/web/src/feature/monitor/hooks.ts index 80cd8e67..3e937199 100644 --- a/web/src/feature/monitor/hooks.ts +++ b/web/src/feature/monitor/hooks.ts @@ -10,8 +10,10 @@ export interface DashboardAggregates { input_amount: number image_input_amount: number audio_input_amount: number + video_input_amount: number output_amount: number image_output_amount: number + audio_output_amount: number cached_amount: number cache_creation_amount: number web_search_amount: number @@ -21,8 +23,10 @@ export interface DashboardAggregates { input_tokens: number image_input_tokens: number audio_input_tokens: number + video_input_tokens: number output_tokens: number image_output_tokens: number + audio_output_tokens: number cached_tokens: number cache_creation_tokens: number cache_hit_count: number @@ -64,8 +68,10 @@ function withSummaryDefaults(summary?: ModelSummary): ModelSummary { input_amount: summary?.input_amount || 0, image_input_amount: summary?.image_input_amount || 0, audio_input_amount: summary?.audio_input_amount || 0, + video_input_amount: summary?.video_input_amount || 0, output_amount: summary?.output_amount || 0, image_output_amount: summary?.image_output_amount || 0, + audio_output_amount: summary?.audio_output_amount || 0, cached_amount: summary?.cached_amount || 0, cache_creation_amount: summary?.cache_creation_amount || 0, web_search_amount: summary?.web_search_amount || 0, @@ -87,8 +93,10 @@ function withSummaryDefaults(summary?: ModelSummary): ModelSummary { input_tokens: summary?.input_tokens || 0, image_input_tokens: summary?.image_input_tokens || 0, audio_input_tokens: summary?.audio_input_tokens || 0, + video_input_tokens: summary?.video_input_tokens || 0, output_tokens: summary?.output_tokens || 0, image_output_tokens: summary?.image_output_tokens || 0, + audio_output_tokens: summary?.audio_output_tokens || 0, cached_tokens: summary?.cached_tokens || 0, cache_creation_tokens: summary?.cache_creation_tokens || 0, reasoning_tokens: summary?.reasoning_tokens || 0, @@ -161,8 +169,10 @@ function toChartData(timeSeries: TimeSeriesPoint[], timespan?: string, hasModelF const inputTokens = summary.reduce((acc, s) => acc + (s?.input_tokens || 0), 0) const imageInputTokens = summary.reduce((acc, s) => acc + (s?.image_input_tokens || 0), 0) const audioInputTokens = summary.reduce((acc, s) => acc + (s?.audio_input_tokens || 0), 0) + const videoInputTokens = summary.reduce((acc, s) => acc + (s?.video_input_tokens || 0), 0) const outputTokens = summary.reduce((acc, s) => acc + (s?.output_tokens || 0), 0) const imageOutputTokens = summary.reduce((acc, s) => acc + (s?.image_output_tokens || 0), 0) + const audioOutputTokens = summary.reduce((acc, s) => acc + (s?.audio_output_tokens || 0), 0) const cachedTokens = summary.reduce((acc, s) => acc + (s?.cached_tokens || 0), 0) const cacheCreationTokens = summary.reduce((acc, s) => acc + (s?.cache_creation_tokens || 0), 0) const cacheHitCount = summary.reduce((acc, s) => acc + (s?.cache_hit_count || 0), 0) @@ -175,18 +185,20 @@ function toChartData(timeSeries: TimeSeriesPoint[], timespan?: string, hasModelF const inputAmount = summary.reduce((acc, s) => acc + (s?.input_amount || 0), 0) const imageInputAmount = summary.reduce((acc, s) => acc + (s?.image_input_amount || 0), 0) const audioInputAmount = summary.reduce((acc, s) => acc + (s?.audio_input_amount || 0), 0) + const videoInputAmount = summary.reduce((acc, s) => acc + (s?.video_input_amount || 0), 0) const outputAmount = summary.reduce((acc, s) => acc + (s?.output_amount || 0), 0) const imageOutputAmount = summary.reduce((acc, s) => acc + (s?.image_output_amount || 0), 0) + const audioOutputAmount = summary.reduce((acc, s) => acc + (s?.audio_output_amount || 0), 0) const cachedAmount = summary.reduce((acc, s) => acc + (s?.cached_amount || 0), 0) const cacheCreationAmount = summary.reduce((acc, s) => acc + (s?.cache_creation_amount || 0), 0) const webSearchAmount = summary.reduce((acc, s) => acc + (s?.web_search_amount || 0), 0) const usedAmount = summary.reduce((acc, s) => acc + (s?.used_amount || 0), 0) - const totalInputAmount = inputAmount + imageInputAmount + audioInputAmount + cachedAmount + cacheCreationAmount - const totalOutputAmount = outputAmount + imageOutputAmount + const totalInputAmount = inputAmount + imageInputAmount + audioInputAmount + videoInputAmount + cachedAmount + cacheCreationAmount + const totalOutputAmount = outputAmount + imageOutputAmount + audioOutputAmount // Non-overlapping text portions (subtract sub-categories from totals) - const textInputTokens = Math.max(0, inputTokens - imageInputTokens - audioInputTokens - cachedTokens - cacheCreationTokens) - const textOutputTokens = Math.max(0, outputTokens - imageOutputTokens) + const textInputTokens = Math.max(0, inputTokens - imageInputTokens - audioInputTokens - videoInputTokens - cachedTokens - cacheCreationTokens) + const textOutputTokens = Math.max(0, outputTokens - imageOutputTokens - audioOutputTokens) const status2xxCount = summary.reduce((acc, s) => acc + (s?.status_2xx_count || 0), 0) const status4xxCount = summary.reduce((acc, s) => acc + (s?.status_4xx_count || 0), 0) @@ -246,9 +258,11 @@ function toChartData(timeSeries: TimeSeriesPoint[], timespan?: string, hasModelF textInputTokens, imageInputTokens, audioInputTokens, + videoInputTokens, outputTokens, textOutputTokens, imageOutputTokens, + audioOutputTokens, cachedTokens, cacheCreationTokens, cacheHitCount, @@ -261,9 +275,11 @@ function toChartData(timeSeries: TimeSeriesPoint[], timespan?: string, hasModelF totalInputAmount, imageInputAmount, audioInputAmount, + videoInputAmount, outputAmount, totalOutputAmount, imageOutputAmount, + audioOutputAmount, cachedAmount, cacheCreationAmount, webSearchAmount, @@ -285,7 +301,7 @@ function computeDashboardResult( // Transform time series based on data source let timeSeries = originalTimeSeries - let isTransformedDataSource = dataSource && dataSource !== 'total' + const isTransformedDataSource = dataSource && dataSource !== 'total' if (isTransformedDataSource) { timeSeries = originalTimeSeries.map(ts => { @@ -317,36 +333,39 @@ function computeDashboardResult( exception_count: dataSet.exception_count || 0, retry_count: dataSet.retry_count || 0, input_tokens: dataSet.input_tokens || 0, + image_input_tokens: dataSet.image_input_tokens || 0, + audio_input_tokens: dataSet.audio_input_tokens || 0, + video_input_tokens: dataSet.video_input_tokens || 0, output_tokens: dataSet.output_tokens || 0, + image_output_tokens: dataSet.image_output_tokens || 0, + audio_output_tokens: dataSet.audio_output_tokens || 0, + cached_tokens: dataSet.cached_tokens || 0, + cache_creation_tokens: dataSet.cache_creation_tokens || 0, + reasoning_tokens: dataSet.reasoning_tokens || 0, total_tokens: dataSet.total_tokens || 0, + web_search_count: dataSet.web_search_count || 0, used_amount: dataSet.used_amount || 0, input_amount: dataSet.input_amount || 0, + image_input_amount: dataSet.image_input_amount || 0, + audio_input_amount: dataSet.audio_input_amount || 0, + video_input_amount: dataSet.video_input_amount || 0, output_amount: dataSet.output_amount || 0, + image_output_amount: dataSet.image_output_amount || 0, + audio_output_amount: dataSet.audio_output_amount || 0, + cached_amount: dataSet.cached_amount || 0, + cache_creation_amount: dataSet.cache_creation_amount || 0, + web_search_amount: dataSet.web_search_amount || 0, status_2xx_count: dataSet.status_2xx_count || 0, status_4xx_count: dataSet.status_4xx_count || 0, status_5xx_count: dataSet.status_5xx_count || 0, + status_other_count: dataSet.status_other_count || 0, + status_400_count: dataSet.status_400_count || 0, + status_429_count: dataSet.status_429_count || 0, + status_500_count: dataSet.status_500_count || 0, cache_hit_count: dataSet.cache_hit_count || 0, - cached_tokens: dataSet.cached_tokens || 0, - // Reset other fields that aren't in SummaryDataSet - image_input_tokens: 0, - audio_input_tokens: 0, - image_output_tokens: 0, - cache_creation_tokens: 0, - reasoning_tokens: 0, - image_input_amount: 0, - audio_input_amount: 0, - image_output_amount: 0, - cached_amount: 0, - cache_creation_amount: 0, - web_search_amount: 0, - total_time_milliseconds: 0, - total_ttfb_milliseconds: 0, - status_other_count: 0, - status_400_count: 0, - status_429_count: 0, - status_500_count: 0, - cache_creation_count: 0, - web_search_count: 0, + cache_creation_count: dataSet.cache_creation_count || 0, + total_time_milliseconds: dataSet.total_time_milliseconds || 0, + total_ttfb_milliseconds: dataSet.total_ttfb_milliseconds || 0, max_rpm: 0, max_tpm: 0, } @@ -365,8 +384,10 @@ function computeDashboardResult( input_amount: 0, image_input_amount: 0, audio_input_amount: 0, + video_input_amount: 0, output_amount: 0, image_output_amount: 0, + audio_output_amount: 0, cached_amount: 0, cache_creation_amount: 0, web_search_amount: 0, @@ -376,8 +397,10 @@ function computeDashboardResult( input_tokens: 0, image_input_tokens: 0, audio_input_tokens: 0, + video_input_tokens: 0, output_tokens: 0, image_output_tokens: 0, + audio_output_tokens: 0, cached_tokens: 0, cache_creation_tokens: 0, cache_hit_count: 0, @@ -408,17 +431,29 @@ function computeDashboardResult( existing.input_amount = (existing?.input_amount || 0) + (normalized?.input_amount || 0) existing.image_input_amount = (existing?.image_input_amount || 0) + (normalized?.image_input_amount || 0) existing.audio_input_amount = (existing?.audio_input_amount || 0) + (normalized?.audio_input_amount || 0) + existing.video_input_amount = (existing?.video_input_amount || 0) + (normalized?.video_input_amount || 0) existing.output_amount = (existing?.output_amount || 0) + (normalized?.output_amount || 0) existing.image_output_amount = (existing?.image_output_amount || 0) + (normalized?.image_output_amount || 0) + existing.audio_output_amount = (existing?.audio_output_amount || 0) + (normalized?.audio_output_amount || 0) existing.cached_amount = (existing?.cached_amount || 0) + (normalized?.cached_amount || 0) existing.cache_creation_amount = (existing?.cache_creation_amount || 0) + (normalized?.cache_creation_amount || 0) existing.web_search_amount = (existing?.web_search_amount || 0) + (normalized?.web_search_amount || 0) existing.total_time_milliseconds = (existing?.total_time_milliseconds || 0) + (normalized?.total_time_milliseconds || 0) existing.total_ttfb_milliseconds = (existing?.total_ttfb_milliseconds || 0) + (normalized?.total_ttfb_milliseconds || 0) existing.input_tokens = (existing?.input_tokens || 0) + (normalized?.input_tokens || 0) + existing.image_input_tokens = (existing?.image_input_tokens || 0) + (normalized?.image_input_tokens || 0) + existing.audio_input_tokens = (existing?.audio_input_tokens || 0) + (normalized?.audio_input_tokens || 0) + existing.video_input_tokens = (existing?.video_input_tokens || 0) + (normalized?.video_input_tokens || 0) existing.output_tokens = (existing?.output_tokens || 0) + (normalized?.output_tokens || 0) + existing.image_output_tokens = (existing?.image_output_tokens || 0) + (normalized?.image_output_tokens || 0) + existing.audio_output_tokens = (existing?.audio_output_tokens || 0) + (normalized?.audio_output_tokens || 0) existing.cached_tokens = (existing?.cached_tokens || 0) + (normalized?.cached_tokens || 0) + existing.cache_creation_tokens = (existing?.cache_creation_tokens || 0) + (normalized?.cache_creation_tokens || 0) + existing.cache_hit_count = (existing?.cache_hit_count || 0) + (normalized?.cache_hit_count || 0) + existing.cache_creation_count = (existing?.cache_creation_count || 0) + (normalized?.cache_creation_count || 0) + existing.reasoning_tokens = (existing?.reasoning_tokens || 0) + (normalized?.reasoning_tokens || 0) existing.total_tokens = (existing?.total_tokens || 0) + (normalized?.total_tokens || 0) + existing.web_search_count = (existing?.web_search_count || 0) + (normalized?.web_search_count || 0) if ((normalized?.max_rpm || 0) > (existing?.max_rpm || 0)) existing.max_rpm = normalized?.max_rpm || 0 if ((normalized?.max_tpm || 0) > (existing?.max_tpm || 0)) existing.max_tpm = normalized?.max_tpm || 0 } else { @@ -437,12 +472,37 @@ function computeDashboardResult( status_2xx_count: (target.status_2xx_count || 0) + (source.status_2xx_count || 0), status_4xx_count: (target.status_4xx_count || 0) + (source.status_4xx_count || 0), status_5xx_count: (target.status_5xx_count || 0) + (source.status_5xx_count || 0), + status_other_count: (target.status_other_count || 0) + (source.status_other_count || 0), + status_400_count: (target.status_400_count || 0) + (source.status_400_count || 0), + status_429_count: (target.status_429_count || 0) + (source.status_429_count || 0), + status_500_count: (target.status_500_count || 0) + (source.status_500_count || 0), + cache_hit_count: (target.cache_hit_count || 0) + (source.cache_hit_count || 0), + cache_creation_count: (target.cache_creation_count || 0) + (source.cache_creation_count || 0), input_tokens: (target.input_tokens || 0) + (source.input_tokens || 0), + image_input_tokens: (target.image_input_tokens || 0) + (source.image_input_tokens || 0), + audio_input_tokens: (target.audio_input_tokens || 0) + (source.audio_input_tokens || 0), + video_input_tokens: (target.video_input_tokens || 0) + (source.video_input_tokens || 0), output_tokens: (target.output_tokens || 0) + (source.output_tokens || 0), + image_output_tokens: (target.image_output_tokens || 0) + (source.image_output_tokens || 0), + audio_output_tokens: (target.audio_output_tokens || 0) + (source.audio_output_tokens || 0), + cached_tokens: (target.cached_tokens || 0) + (source.cached_tokens || 0), + cache_creation_tokens: (target.cache_creation_tokens || 0) + (source.cache_creation_tokens || 0), + reasoning_tokens: (target.reasoning_tokens || 0) + (source.reasoning_tokens || 0), total_tokens: (target.total_tokens || 0) + (source.total_tokens || 0), + web_search_count: (target.web_search_count || 0) + (source.web_search_count || 0), used_amount: (target.used_amount || 0) + (source.used_amount || 0), input_amount: (target.input_amount || 0) + (source.input_amount || 0), + image_input_amount: (target.image_input_amount || 0) + (source.image_input_amount || 0), + audio_input_amount: (target.audio_input_amount || 0) + (source.audio_input_amount || 0), + video_input_amount: (target.video_input_amount || 0) + (source.video_input_amount || 0), output_amount: (target.output_amount || 0) + (source.output_amount || 0), + image_output_amount: (target.image_output_amount || 0) + (source.image_output_amount || 0), + audio_output_amount: (target.audio_output_amount || 0) + (source.audio_output_amount || 0), + cached_amount: (target.cached_amount || 0) + (source.cached_amount || 0), + cache_creation_amount: (target.cache_creation_amount || 0) + (source.cache_creation_amount || 0), + web_search_amount: (target.web_search_amount || 0) + (source.web_search_amount || 0), + total_time_milliseconds: (target.total_time_milliseconds || 0) + (source.total_time_milliseconds || 0), + total_ttfb_milliseconds: (target.total_ttfb_milliseconds || 0) + (source.total_ttfb_milliseconds || 0), } } @@ -480,8 +540,10 @@ function computeDashboardResult( agg.input_amount += normalized?.input_amount || 0 agg.image_input_amount += normalized?.image_input_amount || 0 agg.audio_input_amount += normalized?.audio_input_amount || 0 + agg.video_input_amount += normalized?.video_input_amount || 0 agg.output_amount += normalized?.output_amount || 0 agg.image_output_amount += normalized?.image_output_amount || 0 + agg.audio_output_amount += normalized?.audio_output_amount || 0 agg.cached_amount += normalized?.cached_amount || 0 agg.cache_creation_amount += normalized?.cache_creation_amount || 0 agg.web_search_amount += normalized?.web_search_amount || 0 @@ -490,8 +552,10 @@ function computeDashboardResult( agg.input_tokens += normalized?.input_tokens || 0 agg.image_input_tokens += normalized?.image_input_tokens || 0 agg.audio_input_tokens += normalized?.audio_input_tokens || 0 + agg.video_input_tokens += normalized?.video_input_tokens || 0 agg.output_tokens += normalized?.output_tokens || 0 agg.image_output_tokens += normalized?.image_output_tokens || 0 + agg.audio_output_tokens += normalized?.audio_output_tokens || 0 agg.cached_tokens += normalized?.cached_tokens || 0 agg.cache_creation_tokens += normalized?.cache_creation_tokens || 0 agg.cache_hit_count += normalized?.cache_hit_count || 0 diff --git a/web/src/types/dashboard.ts b/web/src/types/dashboard.ts index 5d8b355e..487b4111 100644 --- a/web/src/types/dashboard.ts +++ b/web/src/types/dashboard.ts @@ -8,8 +8,10 @@ export interface ModelSummary { input_amount?: number image_input_amount?: number audio_input_amount?: number + video_input_amount?: number output_amount?: number image_output_amount?: number + audio_output_amount?: number cached_amount?: number cache_creation_amount?: number web_search_amount?: number @@ -31,8 +33,10 @@ export interface ModelSummary { input_tokens?: number image_input_tokens?: number audio_input_tokens?: number + video_input_tokens?: number output_tokens?: number image_output_tokens?: number + audio_output_tokens?: number cached_tokens?: number cache_creation_tokens?: number reasoning_tokens?: number @@ -62,8 +66,10 @@ export interface SummaryDataSet { input_tokens?: number image_input_tokens?: number audio_input_tokens?: number + video_input_tokens?: number output_tokens?: number image_output_tokens?: number + audio_output_tokens?: number cached_tokens?: number cache_creation_tokens?: number reasoning_tokens?: number @@ -72,8 +78,10 @@ export interface SummaryDataSet { input_amount?: number image_input_amount?: number audio_input_amount?: number + video_input_amount?: number output_amount?: number image_output_amount?: number + audio_output_amount?: number cached_amount?: number cache_creation_amount?: number web_search_amount?: number @@ -106,9 +114,11 @@ export interface ChartDataPoint { textInputTokens: number imageInputTokens: number audioInputTokens: number + videoInputTokens: number outputTokens: number textOutputTokens: number imageOutputTokens: number + audioOutputTokens: number cachedTokens: number cacheCreationTokens: number cacheHitCount: number @@ -121,9 +131,11 @@ export interface ChartDataPoint { totalInputAmount: number imageInputAmount: number audioInputAmount: number + videoInputAmount: number outputAmount: number totalOutputAmount: number imageOutputAmount: number + audioOutputAmount: number cachedAmount: number cacheCreationAmount: number webSearchAmount: number diff --git a/web/src/types/log.ts b/web/src/types/log.ts index 563cc56c..982c74d0 100644 --- a/web/src/types/log.ts +++ b/web/src/types/log.ts @@ -8,10 +8,18 @@ export interface LogPrice { cached_price_unit: number image_input_price: number image_input_price_unit: number + audio_input_price: number + audio_input_price_unit: number + video_input_price: number + video_input_price_unit: number input_price: number input_price_unit: number output_price: number output_price_unit: number + image_output_price: number + image_output_price_unit: number + audio_output_price: number + audio_output_price_unit: number per_request_price: number thinking_mode_output_price: number thinking_mode_output_price_unit: number @@ -24,8 +32,12 @@ export interface LogUsage { cache_creation_tokens: number cached_tokens: number image_input_tokens: number + audio_input_tokens: number + video_input_tokens: number input_tokens: number output_tokens: number + image_output_tokens: number + audio_output_tokens: number reasoning_tokens: number total_tokens: number web_search_count: number @@ -36,8 +48,10 @@ export interface LogAmount { input_amount?: number image_input_amount?: number audio_input_amount?: number + video_input_amount?: number output_amount?: number image_output_amount?: number + audio_output_amount?: number cached_amount?: number cache_creation_amount?: number web_search_amount?: number diff --git a/web/src/types/model.ts b/web/src/types/model.ts index a244fb0d..d5f4f624 100644 --- a/web/src/types/model.ts +++ b/web/src/types/model.ts @@ -48,6 +48,10 @@ export interface ModelPrice { image_output_price_unit?: number audio_input_price?: number audio_input_price_unit?: number + video_input_price?: number + video_input_price_unit?: number + audio_output_price?: number + audio_output_price_unit?: number thinking_mode_output_price?: number thinking_mode_output_price_unit?: number web_search_price?: number diff --git a/web/src/validation/model.ts b/web/src/validation/model.ts index aeb5816c..44e81e62 100644 --- a/web/src/validation/model.ts +++ b/web/src/validation/model.ts @@ -103,6 +103,10 @@ const basePriceSchema = z.object({ image_output_price_unit: z.number().optional(), audio_input_price: z.number().optional(), audio_input_price_unit: z.number().optional(), + video_input_price: z.number().optional(), + video_input_price_unit: z.number().optional(), + audio_output_price: z.number().optional(), + audio_output_price_unit: z.number().optional(), thinking_mode_output_price: z.number().optional(), thinking_mode_output_price_unit: z.number().optional(), web_search_price: z.number().optional(), @@ -130,6 +134,10 @@ export const priceSchema = z.object({ image_output_price_unit: z.number().optional(), audio_input_price: z.number().optional(), audio_input_price_unit: z.number().optional(), + video_input_price: z.number().optional(), + video_input_price_unit: z.number().optional(), + audio_output_price: z.number().optional(), + audio_output_price_unit: z.number().optional(), thinking_mode_output_price: z.number().optional(), thinking_mode_output_price_unit: z.number().optional(), web_search_price: z.number().optional(),