Files

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
}