feat(*): 模型渠道 - 支持系统模型图标并升级 Agnes 视频协议
This commit is contained in:
@@ -488,6 +488,7 @@ func asyncAudioAdapter() Adapter {
|
||||
|
||||
func agnesAdapter() Adapter {
|
||||
info := metadata("agnes-video", "Agnes 视频", "Agnes AI", CapabilityVideo, "POST /v1/videos", "GET /agnesapi?video_id={id}&model_name={model}", "application/json")
|
||||
info.Version = "1.2.0"
|
||||
info.Description = "Agnes AI 官方异步视频协议,支持 Agnes Video V2.0、2.5 与 2.5 Flash。"
|
||||
info.RequiresPublicMediaURLs = true
|
||||
info.Parameters = []Parameter{
|
||||
@@ -497,8 +498,8 @@ func agnesAdapter() Adapter {
|
||||
{Name: "videos", Type: "media[]", Mapping: "videos", Description: "仅 Agnes Video 2.5 reference 模式支持,Flash 与 V2.0 不支持。"},
|
||||
{Name: "audios", Type: "media[]", Mapping: "audios", Description: "Agnes Video 2.5 系列 reference 模式支持。"},
|
||||
{Name: "duration", Type: "integer", Mapping: "seconds/num_frames", Description: "2.5 系列映射为字符串 seconds;V2.0 按帧率换算 num_frames。"},
|
||||
{Name: "aspectRatio", Type: "string", Mapping: "aspect_ratio", Description: "2.5 系列画幅比例。"},
|
||||
{Name: "resolution", Type: "string", Mapping: "size", Description: "2.5 支持 720P、960P、2K;Flash 固定 720P。"},
|
||||
{Name: "aspectRatio", Type: "string", Values: []string{"21:9", "16:9", "4:3", "1:1", "3:4", "9:16"}, Mapping: "aspect_ratio", Description: "2.5 系列画幅比例;历史像素尺寸会在插件边界转换为对应比例。"},
|
||||
{Name: "resolution", Type: "string", Values: []string{"720P", "960P", "2K"}, Mapping: "size", Description: "通用层分辨率档位,映射为 Agnes 的 size;Flash 固定 720P。"},
|
||||
}
|
||||
return builtinAdapter{
|
||||
info: info,
|
||||
@@ -542,13 +543,22 @@ func buildAgnesCreate(r GenerationRequest) (RequestSpec, error) {
|
||||
func buildAgnesV25Body(r GenerationRequest) (map[string]any, error) {
|
||||
mode := firstString(r.Extra, "mode")
|
||||
if mode == "" {
|
||||
switch {
|
||||
case len(r.Videos) > 0 || len(r.Audios) > 0:
|
||||
switch strings.TrimSpace(r.Operation) {
|
||||
case "reference_to_video", "audio_to_video":
|
||||
mode = "reference"
|
||||
case len(r.Images) > 0:
|
||||
case "image_to_video":
|
||||
mode = "keyframe"
|
||||
default:
|
||||
mode = "text"
|
||||
switch {
|
||||
case len(r.Videos) > 0 || len(r.Audios) > 0:
|
||||
mode = "reference"
|
||||
case len(r.Images) > 2:
|
||||
mode = "reference"
|
||||
case len(r.Images) > 0:
|
||||
mode = "keyframe"
|
||||
default:
|
||||
mode = "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
if mode != "text" && mode != "keyframe" && mode != "reference" {
|
||||
@@ -558,9 +568,13 @@ func buildAgnesV25Body(r GenerationRequest) (map[string]any, error) {
|
||||
if seconds < 4 || seconds > 12 {
|
||||
return nil, fmt.Errorf("Agnes 2.5 seconds 必须在 4 到 12 秒之间")
|
||||
}
|
||||
size := strings.ToUpper(defaultValue(r.Resolution, firstString(r.Extra, "size")))
|
||||
if size == "" {
|
||||
size = "720P"
|
||||
size, err := normalizeAgnesV25Resolution(defaultValue(r.Resolution, firstString(r.Extra, "size")))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aspectRatio, err := normalizeAgnesV25AspectRatio(defaultValue(r.AspectRatio, firstString(r.Extra, "aspect_ratio")))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.Model == "agnes-video-2.5-flash" {
|
||||
if size != "720P" {
|
||||
@@ -581,7 +595,7 @@ func buildAgnesV25Body(r GenerationRequest) (map[string]any, error) {
|
||||
"mode": mode,
|
||||
"seconds": strconv.Itoa(seconds),
|
||||
"size": size,
|
||||
"aspect_ratio": defaultValue(r.AspectRatio, firstString(r.Extra, "aspect_ratio")),
|
||||
"aspect_ratio": aspectRatio,
|
||||
"n": 1,
|
||||
}
|
||||
if seed, ok := r.Extra["seed"]; ok {
|
||||
@@ -621,6 +635,48 @@ func buildAgnesV25Body(r GenerationRequest) (map[string]any, error) {
|
||||
return compactMap(body), nil
|
||||
}
|
||||
|
||||
func normalizeAgnesV25Resolution(value string) (string, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "", "auto", "720", "720p":
|
||||
return "720P", nil
|
||||
case "960", "960p":
|
||||
return "960P", nil
|
||||
case "2k", "1440", "1440p":
|
||||
return "2K", nil
|
||||
default:
|
||||
return "", fmt.Errorf("Agnes Video 2.5 分辨率必须是 720P、960P 或 2K")
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeAgnesV25AspectRatio(value string) (string, error) {
|
||||
normalized := strings.ToLower(strings.TrimSpace(strings.ReplaceAll(value, "×", "x")))
|
||||
if normalized == "" || normalized == "auto" {
|
||||
return "16:9", nil
|
||||
}
|
||||
ratios := []string{"21:9", "16:9", "4:3", "1:1", "3:4", "9:16"}
|
||||
for _, ratio := range ratios {
|
||||
if normalized == ratio {
|
||||
return ratio, nil
|
||||
}
|
||||
}
|
||||
dimensions := strings.Split(normalized, "x")
|
||||
if len(dimensions) == 2 {
|
||||
width, widthErr := strconv.ParseInt(dimensions[0], 10, 64)
|
||||
height, heightErr := strconv.ParseInt(dimensions[1], 10, 64)
|
||||
if widthErr == nil && heightErr == nil && width > 0 && height > 0 && width <= 1_000_000 && height <= 1_000_000 {
|
||||
for _, ratio := range ratios {
|
||||
parts := strings.Split(ratio, ":")
|
||||
ratioWidth, _ := strconv.ParseInt(parts[0], 10, 64)
|
||||
ratioHeight, _ := strconv.ParseInt(parts[1], 10, 64)
|
||||
if width*ratioHeight == height*ratioWidth {
|
||||
return ratio, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("Agnes Video 2.5 画幅必须是 21:9、16:9、4:3、1:1、3:4 或 9:16")
|
||||
}
|
||||
|
||||
func buildAgnesV20Body(r GenerationRequest) (map[string]any, error) {
|
||||
if len(r.Videos)+len(r.Audios) > 0 {
|
||||
return nil, fmt.Errorf("Agnes Video V2.0 不支持参考视频或音频")
|
||||
|
||||
@@ -248,6 +248,23 @@ func TestAgnesMapsOfficialCreateAndPollContracts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgnesVideo25SeparatesHistoricalPixelSizeFromResolutionTier(t *testing.T) {
|
||||
adapter, _ := Builtins().Get("agnes-video")
|
||||
create, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{
|
||||
Model: "agnes-video-2.5", Prompt: "test", Duration: 5, Resolution: "720", AspectRatio: "1280x720",
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildCreate() error = %v", err)
|
||||
}
|
||||
body := create.Body.(map[string]any)
|
||||
if body["size"] != "720P" || body["aspect_ratio"] != "16:9" {
|
||||
t.Fatalf("Agnes normalized body = %#v", body)
|
||||
}
|
||||
if body["size"] == "1280x720" {
|
||||
t.Fatalf("Agnes size must be a resolution tier: %#v", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgnesFlashRejectsUnsupportedOfficialInputs(t *testing.T) {
|
||||
adapter, _ := Builtins().Get("agnes-video")
|
||||
_, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{Model: "agnes-video-2.5-flash", Prompt: "test", Duration: 5, Resolution: "2K"}})
|
||||
@@ -260,6 +277,21 @@ func TestAgnesFlashRejectsUnsupportedOfficialInputs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgnesVideo25UsesRequestedReferenceOperationForSingleImage(t *testing.T) {
|
||||
adapter, _ := Builtins().Get("agnes-video")
|
||||
spec, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{
|
||||
Model: "agnes-video-2.5", Prompt: "use it as a style reference", Duration: 5, Resolution: "720P", AspectRatio: "16:9",
|
||||
Operation: "reference_to_video", Images: []MediaReference{{URL: "https://cdn.example/reference.png"}},
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildCreate() error = %v", err)
|
||||
}
|
||||
body := spec.Body.(map[string]any)
|
||||
if body["mode"] != "reference" || body["images"] == nil || body["first_frame"] != nil {
|
||||
t.Fatalf("Agnes reference body = %#v", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeclarativeManifestMapsFieldsAndResponses(t *testing.T) {
|
||||
manifest := []byte(`{
|
||||
"apiVersion":"yingce.plugin/v1",
|
||||
|
||||
Reference in New Issue
Block a user