mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
64 lines
2.5 KiB
Go
64 lines
2.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"mime"
|
|
"net/http"
|
|
"time"
|
|
|
|
middleware "github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// SeedanceTasks exposes Ark's native asynchronous video task protocol.
|
|
func (h *OpenAIGatewayHandler) SeedanceTasks(c *gin.Context) {
|
|
if c.Request.Method == http.MethodPost && c.GetHeader("Content-Type") != "" {
|
|
mediaType, _, err := mime.ParseMediaType(c.GetHeader("Content-Type"))
|
|
if err != nil || mediaType != "application/json" {
|
|
h.errorResponse(c, http.StatusUnsupportedMediaType, "invalid_request_error", "Seedance requires application/json")
|
|
return
|
|
}
|
|
}
|
|
key, ok := middleware.GetAPIKeyFromContext(c)
|
|
if !ok || key.Group == nil || (key.Group.Platform != service.PlatformOpenAI && key.Group.Platform != service.PlatformComposite) {
|
|
h.errorResponse(c, http.StatusForbidden, "permission_error", "Seedance requires an OpenAI or composite group")
|
|
return
|
|
}
|
|
endpoint := service.SeedanceEndpointCreate
|
|
setActualUpstreamEndpoint(c, EndpointSeedanceTasks)
|
|
taskID := ""
|
|
if c.Request.Method != http.MethodPost {
|
|
taskID = service.SeedanceTaskKey(c.Param("task_id"))
|
|
endpoint = service.SeedanceEndpointStatus
|
|
if c.Request.Method == http.MethodDelete {
|
|
endpoint = service.SeedanceEndpointDelete
|
|
}
|
|
}
|
|
h.handleGrokMedia(c, endpoint, taskID)
|
|
}
|
|
|
|
// Ark reports actual completion tokens. Never infer tokens from duration or use
|
|
// Grok's per-second video tariff. Repeated polls share the durable task dedup key.
|
|
func prepareSeedanceCompletionBilling(ctx context.Context, h *OpenAIGatewayHandler, key *service.APIKey, subject middleware.AuthSubject, taskID string, result *service.OpenAIForwardResult) *service.OpenAIForwardResult {
|
|
if result == nil || result.Usage.OutputTokens <= 0 {
|
|
return nil
|
|
}
|
|
pending, err := h.gatewayService.LoadGrokVideoPendingBilling(ctx, taskID, subject.UserID, key.ID)
|
|
if err != nil || pending == nil {
|
|
return nil
|
|
}
|
|
claimed, err := h.gatewayService.ClaimGrokVideoBilling(ctx, taskID, subject.UserID, key.ID)
|
|
if err != nil || !claimed {
|
|
return nil
|
|
}
|
|
merged := *result
|
|
merged.Model = pending.Model
|
|
merged.BillingModel = firstNonEmptyString(pending.BillingModel, pending.Model)
|
|
merged.UpstreamModel = firstNonEmptyString(pending.UpstreamModel, result.UpstreamModel)
|
|
merged.RequestID = service.StableGrokVideoBillingRequestID(taskID)
|
|
merged.ResponseID = taskID
|
|
merged.Duration = service.GrokVideoE2EDuration(pending.CreatedAt, time.Now())
|
|
return &merged
|
|
}
|