feat(*): 模型渠道 - 支持系统模型图标并升级 Agnes 视频协议

This commit is contained in:
ddCat
2026-08-27 23:31:38 +08:00
parent 7cb6aed27d
commit b3e977a985
30 changed files with 861 additions and 44 deletions

View File

@@ -834,6 +834,9 @@ func proxySystemRequestPath(c *gin.Context, svc *service.Service, user *model.Us
return
}
modelName := proxyRequestModelForPath(path, c.GetHeader("Content-Type"), body)
if modelName == "" && c.Request.Method == http.MethodGet && path == "/agnesapi" {
modelName = strings.TrimSpace(c.Query("model_name"))
}
protocol := model.ChannelInterfaceType("")
capability := "text"
var channelModel *model.ChannelModel

View File

@@ -65,6 +65,12 @@ func authorizeCustomRelay(method string, target *url.URL, apiFormat string, cont
return errors.New("自定义渠道调用格式无效")
}
if method == http.MethodGet {
if apiFormat == "openai" && requestPath == "/agnesapi" {
if len(query) == 2 && len(query["video_id"]) == 1 && len(query["model_name"]) == 1 && strings.TrimSpace(query.Get("video_id")) != "" && strings.TrimSpace(query.Get("model_name")) != "" {
return nil
}
return errors.New("Agnes 视频查询必须提供 video_id 和 model_name")
}
if customNovitaTaskResultPath.MatchString(requestPath) {
if len(query) == 1 && len(query["task_id"]) == 1 && strings.TrimSpace(query.Get("task_id")) != "" {
return nil
@@ -168,6 +174,23 @@ func authorizeSystemProxy(channel *model.ModelChannel, protocol model.ChannelInt
if method == http.MethodGet && requestPath == "/models" {
return nil
}
if protocol == model.ChannelInterfaceAgnesVideo {
if method == http.MethodGet && requestPath == "/agnesapi" {
return nil
}
if method != http.MethodPost || requestPath != "/videos" {
return errors.New("系统渠道不允许访问该上游接口")
}
mediaType, _, err := mime.ParseMediaType(contentType)
if err != nil || mediaType != "application/json" {
return errors.New("Agnes 视频生成请求必须使用 application/json")
}
modelName := proxyRequestModel(contentType, body)
if modelName == "" || !channelAllowsModel(channel, modelName) {
return errors.New("当前系统渠道未授权该模型")
}
return nil
}
if protocol == model.ChannelInterfaceMiniMaxVideo {
if method == http.MethodGet && systemMiniMaxTaskPath.MatchString(requestPath) {
return nil

View File

@@ -67,6 +67,28 @@ func TestAuthorizeCustomRelayAllowsModelsAndAgentEndpoints(t *testing.T) {
}
}
func TestAuthorizeCustomRelayAllowsOnlyOfficialAgnesPollQuery(t *testing.T) {
target, err := url.Parse("https://apihub.agnes-ai.com/agnesapi?video_id=video-1&model_name=agnes-video-2.5")
if err != nil {
t.Fatal(err)
}
if err := authorizeCustomRelay(http.MethodGet, target, "openai", ""); err != nil {
t.Fatalf("authorizeCustomRelay() Agnes poll error = %v", err)
}
for _, raw := range []string{
"https://apihub.agnes-ai.com/agnesapi?video_id=video-1",
"https://apihub.agnes-ai.com/agnesapi?video_id=video-1&model_name=agnes-video-2.5&extra=1",
} {
invalid, parseErr := url.Parse(raw)
if parseErr != nil {
t.Fatal(parseErr)
}
if err := authorizeCustomRelay(http.MethodGet, invalid, "openai", ""); err == nil {
t.Fatalf("authorizeCustomRelay(%q) should fail", raw)
}
}
}
func TestAuthorizeCustomRelayRejectsArbitraryRequestsAndCredentialQueries(t *testing.T) {
tests := []struct {
method string
@@ -148,6 +170,23 @@ func TestAuthorizeSystemProxyMiniMaxVideoCreateAndPoll(t *testing.T) {
}
}
func TestAuthorizeSystemProxyAgnesVideoCreateAndPoll(t *testing.T) {
channel := &model.ModelChannel{APIFormat: "openai", ModelsJSON: `["agnes-video-2.5"]`}
createBody := []byte(`{"model":"agnes-video-2.5","prompt":"test","mode":"text","seconds":"5","size":"720P"}`)
if err := authorizeSystemProxy(channel, model.ChannelInterfaceAgnesVideo, http.MethodPost, "/videos", "application/json", createBody); err != nil {
t.Fatalf("Agnes create authorization error = %v", err)
}
if err := authorizeSystemProxy(channel, model.ChannelInterfaceAgnesVideo, http.MethodGet, "/agnesapi", "", nil); err != nil {
t.Fatalf("Agnes poll authorization error = %v", err)
}
if err := authorizeSystemProxy(channel, model.ChannelInterfaceAgnesVideo, http.MethodPost, "/videos", "multipart/form-data; boundary=test", createBody); err == nil {
t.Fatal("expected Agnes multipart create to be rejected")
}
if err := authorizeSystemProxy(channel, model.ChannelInterfaceAgnesVideo, http.MethodGet, "/videos/task-1", "", nil); err == nil {
t.Fatal("expected unsupported Agnes poll path to be rejected")
}
}
func TestAuthorizeSystemProxyVolcengineArkImageOnlyAllowsGenerations(t *testing.T) {
body := []byte(`{"model":"doubao-seedream-test"}`)
channel := &model.ModelChannel{APIFormat: "openai", ModelsJSON: `["doubao-seedream-test"]`}