mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-29 23:10:35 +00:00
1. Async task model redirection (aligned with sync tasks):
- Integrate ModelMappedHelper in RelayTaskSubmit after model name
determination, populating OriginModelName / UpstreamModelName on RelayInfo.
- All task adaptors now send UpstreamModelName to upstream providers:
- Gemini & Vertex: BuildRequestURL uses UpstreamModelName.
- Doubao & Ali: BuildRequestBody conditionally overwrites body.Model.
- Vidu, Kling, Hailuo, Jimeng: convertToRequestPayload accepts RelayInfo
and unconditionally uses info.UpstreamModelName.
- Sora: BuildRequestBody parses JSON and multipart bodies to replace
the "model" field with UpstreamModelName.
- Frontend log visibility: LogTaskConsumption and taskBillingOther now
emit is_model_mapped / upstream_model_name in the "other" JSON field.
- Billing safety: RecalculateTaskQuotaByTokens reads model name from
BillingContext.OriginModelName (via taskModelName) instead of
task.Data["model"], preventing billing leaks from upstream model names.
2. Per-call billing (TaskPricePatches lifecycle):
- Rename TaskBillingContext.ModelName → OriginModelName; add PerCallBilling
bool field, populated from TaskPricePatches at submission time.
- settleTaskBillingOnComplete short-circuits when PerCallBilling is true,
skipping both adaptor adjustments and token-based recalculation.
- Remove ModelName from TaskSubmitResult; use relayInfo.OriginModelName
consistently in controller/relay.go for billing context and logging.
3. Multipart retry boundary mismatch fix:
- Root cause: after Sora (or OpenAI audio) rebuilds a multipart body with a
new boundary and overwrites c.Request.Header["Content-Type"], subsequent
calls to ParseMultipartFormReusable on retry would parse the cached
original body with the wrong boundary, causing "NextPart: EOF".
- Fix: ParseMultipartFormReusable now caches the original Content-Type in
gin context key "_original_multipart_ct" on first call and reuses it for
all subsequent parses, making multipart parsing retry-safe globally.
- Sora adaptor reverted to the standard pattern (direct header set/get),
which is now safe thanks to the root fix.
4. Tests:
- task_billing_test.go: update makeTask to use OriginModelName; add
PerCallBilling settlement tests (skip adaptor adjust, skip token recalc);
add non-per-call adaptor adjustment test with refund verification.
95 lines
2.7 KiB
Go
95 lines
2.7 KiB
Go
package controller
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/constant"
|
|
"github.com/QuantumNous/new-api/dto"
|
|
"github.com/QuantumNous/new-api/model"
|
|
"github.com/QuantumNous/new-api/relay"
|
|
"github.com/QuantumNous/new-api/service"
|
|
"github.com/QuantumNous/new-api/types"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// UpdateTaskBulk 薄入口,实际轮询逻辑在 service 层
|
|
func UpdateTaskBulk() {
|
|
service.TaskPollingLoop()
|
|
}
|
|
|
|
func GetAllTask(c *gin.Context) {
|
|
pageInfo := common.GetPageQuery(c)
|
|
|
|
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
|
|
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
|
|
// 解析其他查询参数
|
|
queryParams := model.SyncTaskQueryParams{
|
|
Platform: constant.TaskPlatform(c.Query("platform")),
|
|
TaskID: c.Query("task_id"),
|
|
Status: c.Query("status"),
|
|
Action: c.Query("action"),
|
|
StartTimestamp: startTimestamp,
|
|
EndTimestamp: endTimestamp,
|
|
ChannelID: c.Query("channel_id"),
|
|
}
|
|
|
|
items := model.TaskGetAllTasks(pageInfo.GetStartIdx(), pageInfo.GetPageSize(), queryParams)
|
|
total := model.TaskCountAllTasks(queryParams)
|
|
pageInfo.SetTotal(int(total))
|
|
pageInfo.SetItems(tasksToDto(items, true))
|
|
common.ApiSuccess(c, pageInfo)
|
|
}
|
|
|
|
func GetUserTask(c *gin.Context) {
|
|
pageInfo := common.GetPageQuery(c)
|
|
|
|
userId := c.GetInt("id")
|
|
|
|
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
|
|
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
|
|
|
|
queryParams := model.SyncTaskQueryParams{
|
|
Platform: constant.TaskPlatform(c.Query("platform")),
|
|
TaskID: c.Query("task_id"),
|
|
Status: c.Query("status"),
|
|
Action: c.Query("action"),
|
|
StartTimestamp: startTimestamp,
|
|
EndTimestamp: endTimestamp,
|
|
}
|
|
|
|
items := model.TaskGetAllUserTask(userId, pageInfo.GetStartIdx(), pageInfo.GetPageSize(), queryParams)
|
|
total := model.TaskCountAllUserTask(userId, queryParams)
|
|
pageInfo.SetTotal(int(total))
|
|
pageInfo.SetItems(tasksToDto(items, false))
|
|
common.ApiSuccess(c, pageInfo)
|
|
}
|
|
|
|
func tasksToDto(tasks []*model.Task, fillUser bool) []*dto.TaskDto {
|
|
var userIdMap map[int]*model.UserBase
|
|
if fillUser {
|
|
userIdMap = make(map[int]*model.UserBase)
|
|
userIds := types.NewSet[int]()
|
|
for _, task := range tasks {
|
|
userIds.Add(task.UserId)
|
|
}
|
|
for _, userId := range userIds.Items() {
|
|
cacheUser, err := model.GetUserCache(userId)
|
|
if err == nil {
|
|
userIdMap[userId] = cacheUser
|
|
}
|
|
}
|
|
}
|
|
result := make([]*dto.TaskDto, len(tasks))
|
|
for i, task := range tasks {
|
|
if fillUser {
|
|
if user, ok := userIdMap[task.UserId]; ok {
|
|
task.Username = user.Username
|
|
}
|
|
}
|
|
result[i] = relay.TaskModel2Dto(task)
|
|
}
|
|
return result
|
|
}
|