diff --git a/backend/internal/app/channel_models.go b/backend/internal/app/channel_models.go index fcefe4fff..d105dade0 100644 --- a/backend/internal/app/channel_models.go +++ b/backend/internal/app/channel_models.go @@ -438,7 +438,7 @@ func (s *Service) syncLogicalModelsFromChannelModel(actor *model.User, channelMo } func supportsTokenBilling(capability string, protocol model.ChannelInterfaceType) bool { - return capability == "text" || (capability == "video" && protocol == model.ChannelInterfaceVolcengineArkVideo) + return capability == "text" || (capability == "video" && model.IsVolcengineArkVideoProtocol(protocol)) } func (s *Service) normalizeChannelModelPriceTiers(req ChannelModelRequest, capability string, protocol model.ChannelInterfaceType, fallbackProviderModelKey string) ([]model.ChannelModelPriceTier, error) { diff --git a/backend/internal/app/model_capability.go b/backend/internal/app/model_capability.go index a9b0612ab..240ab5699 100644 --- a/backend/internal/app/model_capability.go +++ b/backend/internal/app/model_capability.go @@ -153,7 +153,7 @@ func DefaultImageCapabilityConfig(protocol string, modelName string) *ImageCapab image.ResponseFormat = ParameterSupport{Supported: true} image.OutputFormat = ParameterSupport{Supported: false} image.MaxOutputs = 1 - case model.ChannelInterfaceVolcengineArkImage: + case model.ChannelInterfaceVolcengineArkImage, model.ChannelInterfaceVolcengineArkAgentPlanImage: image.References.MaskSupported = false image.Quality.Supported = false image.TransparentBackground.Supported = false @@ -229,7 +229,7 @@ func DefaultModelCapabilityConfigForModel(protocol string, modelName string) *Mo case model.ChannelInterfaceGeminiVeo: video.Duration = VideoDurationConfig{Selection: "enum", Values: []int{4, 6, 8}, Default: 6} video.Resolutions = []string{"720p", "1080p"} - case model.ChannelInterfaceVolcengineArkVideo: + case model.ChannelInterfaceVolcengineArkVideo, model.ChannelInterfaceVolcengineArkAgentPlanVideo: video.Operations = append(video.Operations, "reference_to_video", "audio_to_video") video.References.MaxVideos, video.References.MaxAudios = 3, 3 video.References.MaxVideoBytes, video.References.MaxAudioBytes = 200*1024*1024, 15*1024*1024 @@ -798,7 +798,7 @@ func validateVideoTask(profile *VideoCapabilityConfig, input canvasGenerationInp if len(input.ReferenceImages) > profile.References.MaxImages || len(input.ReferenceVideos) > profile.References.MaxVideos || len(input.ReferenceAudios) > profile.References.MaxAudios { return BadAuthRequest("参考素材数量超过当前模型限制") } - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkVideo) && len(input.ReferenceAudios) > 0 && len(input.ReferenceImages) == 0 && len(input.ReferenceVideos) == 0 { + if model.IsVolcengineArkVideoProtocol(model.ChannelInterfaceType(input.Config.InterfaceType)) && len(input.ReferenceAudios) > 0 && len(input.ReferenceImages) == 0 && len(input.ReferenceVideos) == 0 { return BadAuthRequest("火山方舟全模态参考不支持纯音频或文本+音频,请同时添加参考图片或参考视频") } if len(input.ReferenceImages) < profile.References.MinImages { diff --git a/backend/internal/app/price_validation.go b/backend/internal/app/price_validation.go index 76e60d890..060311779 100644 --- a/backend/internal/app/price_validation.go +++ b/backend/internal/app/price_validation.go @@ -14,7 +14,7 @@ func ValidateChannelModelPrice(billingMode string, capability string, protocol m return unitPrice >= 0 case "token": if capability == "video" { - return protocol == model.ChannelInterfaceVolcengineArkVideo && inputPrice >= 0 && outputPrice >= 0 && cachedPrice >= 0 + return model.IsVolcengineArkVideoProtocol(protocol) && inputPrice >= 0 && outputPrice >= 0 && cachedPrice >= 0 } if capability != "" && capability != "text" { return false diff --git a/backend/internal/app/provider.go b/backend/internal/app/provider.go index 6b7b1ff94..3030ef1bf 100644 --- a/backend/internal/app/provider.go +++ b/backend/internal/app/provider.go @@ -464,7 +464,7 @@ type providerMediaHydrationPolicy struct { func providerMediaHydrationPolicyFor(ctx context.Context, input canvasGenerationInput) providerMediaHydrationPolicy { policy := providerMediaHydrationPolicy{preferURL: providerPrefersMediaURLs(input.Config.InterfaceType, input)} switch strings.TrimSpace(input.Config.InterfaceType) { - case string(model.ChannelInterfaceNewAPIVideo), string(model.ChannelInterfaceNewAPIChannel1), string(model.ChannelInterfaceNewAPIChannel2), string(model.ChannelInterfaceVolcengineArkVideo), string(model.ChannelInterfaceMiniMaxVideo): + case string(model.ChannelInterfaceNewAPIVideo), string(model.ChannelInterfaceNewAPIChannel1), string(model.ChannelInterfaceNewAPIChannel2), string(model.ChannelInterfaceVolcengineArkVideo), string(model.ChannelInterfaceVolcengineArkAgentPlanVideo), string(model.ChannelInterfaceMiniMaxVideo): policy.requireURL = true policy.preferURL = true } @@ -488,11 +488,11 @@ func providerPrefersMediaURLs(interfaceType string, input canvasGenerationInput) } switch strings.TrimSpace(interfaceType) { case string(model.ChannelInterfaceChatCompletion), string(model.ChannelInterfaceOpenAIResponse), string(model.ChannelInterfaceClaudeAPI), - string(model.ChannelInterfaceGrokImage), string(model.ChannelInterfaceVolcengineArkImage), + string(model.ChannelInterfaceGrokImage), string(model.ChannelInterfaceVolcengineArkImage), string(model.ChannelInterfaceVolcengineArkAgentPlanImage), string(model.ChannelInterfaceXAIVideo), string(model.ChannelInterfaceNovitaVideo), string(model.ChannelInterfaceMiniMaxVideo), string(model.ChannelInterfaceNewAPIVideo), string(model.ChannelInterfaceNewAPIChannel1), string(model.ChannelInterfaceNewAPIChannel2), - string(model.ChannelInterfaceVolcengineArkVideo): + string(model.ChannelInterfaceVolcengineArkVideo), string(model.ChannelInterfaceVolcengineArkAgentPlanVideo): return true } if isGrokVideoConfig(input.Config) || isSeedanceVideoConfig(input.Config) || isArkPlanVideoConfig(input.Config) { diff --git a/backend/internal/app/provider_image.go b/backend/internal/app/provider_image.go index e1b3886af..38d1b27d1 100644 --- a/backend/internal/app/provider_image.go +++ b/backend/internal/app/provider_image.go @@ -28,7 +28,7 @@ func runImageTask(ctx context.Context, input canvasGenerationInput) (map[string] if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineJiMengImage) { return runVolcengineJiMengImageTask(ctx, input) } - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkImage) { + if model.IsVolcengineArkImageProtocol(model.ChannelInterfaceType(input.Config.InterfaceType)) { return runVolcengineArkImageTask(ctx, input) } if input.Config.InterfaceType == string(model.ChannelInterfaceGeminiImage) { diff --git a/backend/internal/app/provider_task_cancellation.go b/backend/internal/app/provider_task_cancellation.go index 59b357aa4..1e9ac04d2 100644 --- a/backend/internal/app/provider_task_cancellation.go +++ b/backend/internal/app/provider_task_cancellation.go @@ -194,15 +194,15 @@ func (s *Service) providerCancellationInput(task *model.Task) (canvasGenerationI } func supportsProviderCancellation(interfaceType string) bool { - return interfaceType == string(model.ChannelInterfaceGeminiVeo) || interfaceType == string(model.ChannelInterfaceVolcengineArkVideo) + return interfaceType == string(model.ChannelInterfaceGeminiVeo) || model.IsVolcengineArkVideoProtocol(model.ChannelInterfaceType(interfaceType)) } func cancelProviderTask(ctx context.Context, config providerConfig, providerRequestID string) error { - switch config.InterfaceType { - case string(model.ChannelInterfaceGeminiVeo): + switch { + case config.InterfaceType == string(model.ChannelInterfaceGeminiVeo): path := "/" + strings.TrimLeft(providerRequestID, "/") + ":cancel" return postGeminiJSON(ctx, config, path, map[string]any{}, &map[string]any{}) - case string(model.ChannelInterfaceVolcengineArkVideo): + case model.IsVolcengineArkVideoProtocol(model.ChannelInterfaceType(config.InterfaceType)): path := "/contents/generations/tasks/" + url.PathEscape(providerRequestID) return deleteProviderTask(ctx, config, path) default: @@ -228,8 +228,8 @@ func deleteProviderTask(ctx context.Context, config providerConfig, path string) } func queryProviderCancellation(ctx context.Context, config providerConfig, providerRequestID string) (providerCancellationOutcome, string, error) { - switch config.InterfaceType { - case string(model.ChannelInterfaceGeminiVeo): + switch { + case config.InterfaceType == string(model.ChannelInterfaceGeminiVeo): // Gemini 用 operation.error 表示取消终态,此处必须保留该字段再判断, // 不能让通用 JSON 解包提前把它转换成请求失败。 var operation geminiOperation @@ -248,7 +248,7 @@ func queryProviderCancellation(ctx context.Context, config providerConfig, provi return providerCancellationFailed, firstNonEmpty(message, "failed"), nil } return providerCancellationSucceeded, "succeeded", nil - case string(model.ChannelInterfaceVolcengineArkVideo): + case model.IsVolcengineArkVideoProtocol(model.ChannelInterfaceType(config.InterfaceType)): var state map[string]any if err := getJSON(ctx, config, "/contents/generations/tasks/"+url.PathEscape(providerRequestID), &state); err != nil { return "", "", err diff --git a/backend/internal/app/provider_video.go b/backend/internal/app/provider_video.go index 51884a221..f09410cad 100644 --- a/backend/internal/app/provider_video.go +++ b/backend/internal/app/provider_video.go @@ -287,7 +287,7 @@ func runSeedanceVideosTask(ctx context.Context, input canvasGenerationInput, pol func runSeedanceAgentPlanVideoTask(ctx context.Context, input canvasGenerationInput, pollPolicy videoPollPolicy) (map[string]interface{}, error) { providerName := "Seedance" - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkVideo) { + if model.IsVolcengineArkVideoProtocol(model.ChannelInterfaceType(input.Config.InterfaceType)) { providerName = "火山方舟" } // Agent Plan 与 Videos API 使用相同的恢复合同:已有任务只查询,不重复创建。 @@ -299,7 +299,7 @@ func runSeedanceAgentPlanVideoTask(ctx context.Context, input canvasGenerationIn if err != nil { return nil, err } - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkVideo) { + if model.IsVolcengineArkVideoProtocol(model.ChannelInterfaceType(input.Config.InterfaceType)) { for _, item := range content { if item["type"] == "image_url" { item["role"] = "reference_image" diff --git a/backend/internal/generation/registry.go b/backend/internal/generation/registry.go index 4d4673640..451639aef 100644 --- a/backend/internal/generation/registry.go +++ b/backend/internal/generation/registry.go @@ -70,7 +70,7 @@ func OfficialDeclarativeVideoInterface(interfaceType string) (string, bool) { return "NewAPI 媒体任务", true case string(model.ChannelInterfaceXAIVideo): return "xAI", true - case string(model.ChannelInterfaceVolcengineArkVideo): + case string(model.ChannelInterfaceVolcengineArkVideo), string(model.ChannelInterfaceVolcengineArkAgentPlanVideo): return "火山方舟", true case string(model.ChannelInterfaceVolcengineJiMengVideo): return "即梦", true @@ -91,6 +91,8 @@ func OfficialDeclarativeImageInterface(interfaceType string) (string, bool) { return "Grok Images", true case string(model.ChannelInterfaceVolcengineArkImage): return "火山方舟图片", true + case string(model.ChannelInterfaceVolcengineArkAgentPlanImage): + return "火山方舟 Agent Plan 图片", true case string(model.ChannelInterfaceVolcengineJiMengImage): return "即梦图片", true default: diff --git a/backend/internal/handler/security.go b/backend/internal/handler/security.go index 487c305e9..b1417f77e 100644 --- a/backend/internal/handler/security.go +++ b/backend/internal/handler/security.go @@ -244,11 +244,11 @@ func interfaceAllowsProxyPath(interfaceType model.ChannelInterfaceType, requestP return requestPath == "/messages" case model.ChannelInterfaceOpenAIImage, model.ChannelInterfaceGrokImage: return requestPath == "/images/generations" || requestPath == "/images/edits" - case model.ChannelInterfaceVolcengineArkImage: + case model.ChannelInterfaceVolcengineArkImage, model.ChannelInterfaceVolcengineArkAgentPlanImage: return requestPath == "/images/generations" case model.ChannelInterfaceOpenAIAudio: return requestPath == "/audio/speech" - case model.ChannelInterfaceAsyncAudio, model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2, model.ChannelInterfaceXAIVideo, model.ChannelInterfaceVolcengineArkVideo, model.ChannelInterfaceVolcengineJiMengImage, model.ChannelInterfaceVolcengineJiMengVideo, model.ChannelInterfaceGeminiVeo, model.ChannelInterfaceGeminiImage, model.ChannelInterfaceNovitaVideo, model.ChannelInterfaceMiniMaxVideo: + case model.ChannelInterfaceAsyncAudio, model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2, model.ChannelInterfaceXAIVideo, model.ChannelInterfaceVolcengineArkVideo, model.ChannelInterfaceVolcengineArkAgentPlanVideo, model.ChannelInterfaceVolcengineJiMengImage, model.ChannelInterfaceVolcengineJiMengVideo, model.ChannelInterfaceGeminiVeo, model.ChannelInterfaceGeminiImage, model.ChannelInterfaceNovitaVideo, model.ChannelInterfaceMiniMaxVideo: return false default: return true diff --git a/backend/internal/model/models.go b/backend/internal/model/models.go index d9e8d36d3..f9bb461bc 100644 --- a/backend/internal/model/models.go +++ b/backend/internal/model/models.go @@ -63,16 +63,18 @@ const ( ChannelInterfaceClaudeAPI ChannelInterfaceType = "claude-api" ChannelInterfaceOpenAIImage ChannelInterfaceType = "openai-image" ChannelInterfaceGrokImage ChannelInterfaceType = "grok-image" - ChannelInterfaceVolcengineArkImage ChannelInterfaceType = "volcengine-ark-image" - ChannelInterfaceVolcengineJiMengImage ChannelInterfaceType = "volcengine-jimeng-image" - ChannelInterfaceGeminiImage ChannelInterfaceType = "gemini-image" - ChannelInterfaceOpenAIAudio ChannelInterfaceType = "openai-audio" - ChannelInterfaceAsyncAudio ChannelInterfaceType = "async-audio" - ChannelInterfaceNewAPIVideo ChannelInterfaceType = "newapi" - ChannelInterfaceNewAPIChannel1 ChannelInterfaceType = "newapi-channel-1" - ChannelInterfaceNewAPIChannel2 ChannelInterfaceType = "newapi-channel-2" - ChannelInterfaceXAIVideo ChannelInterfaceType = "xai-video" - ChannelInterfaceVolcengineArkVideo ChannelInterfaceType = "volcengine-ark-video" + ChannelInterfaceVolcengineArkImage ChannelInterfaceType = "volcengine-ark-image" + ChannelInterfaceVolcengineArkAgentPlanImage ChannelInterfaceType = "volcengine-ark-agent-plan-image" + ChannelInterfaceVolcengineJiMengImage ChannelInterfaceType = "volcengine-jimeng-image" + ChannelInterfaceGeminiImage ChannelInterfaceType = "gemini-image" + ChannelInterfaceOpenAIAudio ChannelInterfaceType = "openai-audio" + ChannelInterfaceAsyncAudio ChannelInterfaceType = "async-audio" + ChannelInterfaceNewAPIVideo ChannelInterfaceType = "newapi" + ChannelInterfaceNewAPIChannel1 ChannelInterfaceType = "newapi-channel-1" + ChannelInterfaceNewAPIChannel2 ChannelInterfaceType = "newapi-channel-2" + ChannelInterfaceXAIVideo ChannelInterfaceType = "xai-video" + ChannelInterfaceVolcengineArkVideo ChannelInterfaceType = "volcengine-ark-video" + ChannelInterfaceVolcengineArkAgentPlanVideo ChannelInterfaceType = "volcengine-ark-agent-plan-video" ChannelInterfaceVolcengineJiMengVideo ChannelInterfaceType = "volcengine-jimeng-video" ChannelInterfaceGeminiVeo ChannelInterfaceType = "gemini-veo" ChannelInterfaceNovitaVideo ChannelInterfaceType = "novita-video" diff --git a/backend/internal/model/volcengine_ark.go b/backend/internal/model/volcengine_ark.go new file mode 100644 index 000000000..c333e38c0 --- /dev/null +++ b/backend/internal/model/volcengine_ark.go @@ -0,0 +1,11 @@ +package model + +// IsVolcengineArkImageProtocol 识别官方 Ark 图片与 Agent Plan 图片协议。 +func IsVolcengineArkImageProtocol(protocol ChannelInterfaceType) bool { + return protocol == ChannelInterfaceVolcengineArkImage || protocol == ChannelInterfaceVolcengineArkAgentPlanImage +} + +// IsVolcengineArkVideoProtocol 识别官方 Ark 视频与 Agent Plan 视频协议。 +func IsVolcengineArkVideoProtocol(protocol ChannelInterfaceType) bool { + return protocol == ChannelInterfaceVolcengineArkVideo || protocol == ChannelInterfaceVolcengineArkAgentPlanVideo +} diff --git a/backend/internal/model/volcengine_ark_test.go b/backend/internal/model/volcengine_ark_test.go new file mode 100644 index 000000000..2ee5254f9 --- /dev/null +++ b/backend/internal/model/volcengine_ark_test.go @@ -0,0 +1,24 @@ +package model + +import "testing" + +func TestIsVolcengineArkProtocols(t *testing.T) { + if !IsVolcengineArkImageProtocol(ChannelInterfaceVolcengineArkImage) { + t.Fatal("official ark image should match") + } + if !IsVolcengineArkImageProtocol(ChannelInterfaceVolcengineArkAgentPlanImage) { + t.Fatal("agent plan image should match") + } + if IsVolcengineArkImageProtocol(ChannelInterfaceOpenAIImage) { + t.Fatal("openai image should not match ark image") + } + if !IsVolcengineArkVideoProtocol(ChannelInterfaceVolcengineArkVideo) { + t.Fatal("official ark video should match") + } + if !IsVolcengineArkVideoProtocol(ChannelInterfaceVolcengineArkAgentPlanVideo) { + t.Fatal("agent plan video should match") + } + if IsVolcengineArkVideoProtocol(ChannelInterfaceNewAPIVideo) { + t.Fatal("newapi video should not match ark video") + } +} diff --git a/backend/internal/protocol/docs/volcengine-ark-agent-plan-image.md b/backend/internal/protocol/docs/volcengine-ark-agent-plan-image.md new file mode 100644 index 000000000..c2350a80d --- /dev/null +++ b/backend/internal/protocol/docs/volcengine-ark-agent-plan-image.md @@ -0,0 +1,37 @@ +# 火山方舟 Agent Plan 图片生成 + +本插件面向火山方舟 Agent Plan 专属图片入口。请求体与官方 Ark Seedream 协议一致,但 Base URL 与 API Key 必须来自 Agent Plan 控制台。 + +## 接口、鉴权与区域 + +{{OPERATIONS}} + +```http +POST {channel_base_url}/api/plan/v3/images/generations +Authorization: Bearer +Content-Type: application/json +``` + +与官方 `/api/v3` 的差异: + +| 项 | 官方 Ark | Agent Plan | +| --- | --- | --- | +| Base URL | `https://ark.cn-beijing.volces.com/api/v3` | `https://ark.cn-beijing.volces.com/api/plan/v3` | +| API Key | 推理接入控制台 Key | Agent Plan 专属 Key | +| 计费 | 推理后付费/项目额度 | AFP 套餐抵扣 | +| 请求体 | Seedream images/generations | 同左 | + +不要把官方 Key 打到 `/api/plan/v3`,也不要把 Agent Plan Key 打到 `/api/v3`。 + +## 参数与字段映射 + +{{PARAMETERS}} + +当前实现:`aspectRatio -> size`,所有 `images -> image[]`;`extra` 可透传 `size`、`sequential_image_generation`、`sequential_image_generation_options`、`watermark`。 + +## 官方资料 + +- [火山方舟 Agent Plan 文档](https://www.volcengine.com/docs/82379/2375486) +- [火山方舟视觉模型 API 文档](https://www.volcengine.com/docs/82379) + +{{CONTRACT}} diff --git a/backend/internal/protocol/docs/volcengine-ark-agent-plan-video.md b/backend/internal/protocol/docs/volcengine-ark-agent-plan-video.md new file mode 100644 index 000000000..b3a7d361a --- /dev/null +++ b/backend/internal/protocol/docs/volcengine-ark-agent-plan-video.md @@ -0,0 +1,43 @@ +# 火山方舟 Agent Plan 视频任务 + +本插件使用 Agent Plan 专属 Contents Generations Tasks 异步接口。提示词、参考图、参考视频和参考音频组成 `content[]`;比例、分辨率、时长、生成音频和水印位于顶层任务参数。请求体与官方 Ark Seedance 一致,入口改为 `/api/plan/v3`。 + +## 接口与鉴权 + +{{OPERATIONS}} + +```http +POST {channel_base_url}/api/plan/v3/contents/generations/tasks +GET {channel_base_url}/api/plan/v3/contents/generations/tasks/{task_id} +DELETE {channel_base_url}/api/plan/v3/contents/generations/tasks/{task_id} +Authorization: Bearer +Content-Type: application/json +``` + +与官方 `/api/v3` 的差异: + +| 项 | 官方 Ark | Agent Plan | +| --- | --- | --- | +| Base URL | `https://ark.cn-beijing.volces.com/api/v3` | `https://ark.cn-beijing.volces.com/api/plan/v3` | +| API Key | 推理接入控制台 Key | Agent Plan 专属 Key | +| 计费 | 推理后付费/项目额度 | AFP 套餐抵扣 | +| 请求体 | contents/generations/tasks | 同左 | + +## 参数与字段映射 + +{{PARAMETERS}} + +全模态参考的素材映射如下: + +- 图片:`{"type":"image_url","image_url":{"url":"..."},"role":"reference_image"}` +- 视频:`{"type":"video_url","video_url":{"url":"..."},"role":"reference_video"}` +- 音频:`{"type":"audio_url","audio_url":{"url":"..."},"role":"reference_audio"}` + +单次最多发送 9 张图片、3 个视频和 3 个音频。参考素材可以组合使用,但上游不支持纯音频或“文本 + 音频”。 + +## 官方资料 + +- [火山方舟 Agent Plan 文档](https://www.volcengine.com/docs/82379/2375486) +- [火山方舟内容生成任务文档](https://www.volcengine.com/docs/82379/1520757) + +{{CONTRACT}} diff --git a/backend/internal/protocol/official_catalog_test.go b/backend/internal/protocol/official_catalog_test.go index 3cb15ca9e..db64ef790 100644 --- a/backend/internal/protocol/official_catalog_test.go +++ b/backend/internal/protocol/official_catalog_test.go @@ -599,6 +599,36 @@ func TestOfficialArkSeedreamMapsAspectRatioToPixelSize(t *testing.T) { } } +func TestOfficialArkAgentPlanPluginsUsePlanPaths(t *testing.T) { + image := officialPackageAdapter(t, "volcengine-ark-agent-plan-seedream.yingce-plugin", "volcengine-ark-agent-plan-image") + imageCreate, err := image.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{Model: "doubao-seedream-5-0-260128", Prompt: "circle", AspectRatio: "1:1"}}) + if err != nil { + t.Fatal(err) + } + if imageCreate.Path != "/api/plan/v3/images/generations" { + t.Fatalf("agent plan image create = %#v", imageCreate) + } + if body := manifestTestBody(t, imageCreate); body["size"] != "2048x2048" { + t.Fatalf("agent plan image size = %#v", body["size"]) + } + + video := officialPackageAdapter(t, "volcengine-ark-agent-plan-seedance.yingce-plugin", "volcengine-ark-agent-plan-video") + videoCreate, err := video.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{Model: "doubao-seedance-2-0-260128", Prompt: "walk", AspectRatio: "16:9", Resolution: "720p", Duration: 5}}) + if err != nil { + t.Fatal(err) + } + if videoCreate.Path != "/api/plan/v3/contents/generations/tasks" { + t.Fatalf("agent plan video create = %#v", videoCreate) + } + poll, err := video.BuildPoll(context.Background(), PollContext{TaskID: "task-1", Model: "doubao-seedance-2-0-260128"}) + if err != nil { + t.Fatal(err) + } + if poll.Path != "/api/plan/v3/contents/generations/tasks/task-1" { + t.Fatalf("agent plan video poll = %#v", poll) + } +} + func TestOfficialGeminiImageMapsQualityToImageSize(t *testing.T) { adapter := officialPackageAdapter(t, "google-gemini-image.yingce-plugin", "gemini-image") tests := []struct { diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index 11f78111b..f2a2e2347 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -5,6 +5,13 @@ description: 当前项目已实现功能的手工验证清单与未确认边界 # 待测试 +## 火山方舟 Agent Plan 图片 / 视频插件 + +- 新增官方协议插件:`volcengine-ark-agent-plan-seedream`(provider `volcengine-ark-agent-plan-image`)与 `volcengine-ark-agent-plan-seedance`(provider `volcengine-ark-agent-plan-video`)。 +- 与官方 Ark 插件的差异只在接入面:默认 Base URL 为 `https://ark.cn-beijing.volces.com/api/plan/v3`,创建路径分别为 `/api/plan/v3/images/generations` 与 `/api/plan/v3/contents/generations/tasks`;请求体、参考素材 role、能力档位与官方 Seedream/Seedance 对齐。必须使用 Agent Plan 专属 API Key,不可与 `/api/v3` 官方 Key 混用。 +- 宿主已把 Agent Plan 协议纳入与官方 Ark 相同的能力默认值、Token 计费校验、方舟素材引用、取消任务与前端 payload 分支。 +- 待管理端安装这两个 `.yingce-plugin` 后:分别创建图片/视频渠道,确认默认 Base URL 为 `/api/plan/v3`;用 Agent Plan Key 文生图、参考图生图、Seedance 文生视频与全模态参考成功;故意混用官方 Key 应失败;官方 `volcengine-ark-*` 渠道行为不受影响。 + ## Agent Plan Seedream 误走可信素材(2026-09-17) 根因:`isArkPlanVideoConfig` / 前端 `usesArkVideoAssetReference` 仅按 Base URL 含 `/api/plan/v3` 判断,Agent Plan Seedream 与 Seedance 共用该路径,图片生成被误判为视频可信素材路径,自动 CreateAsset 并把参考图改成 `asset://`,上游图片接口拒绝。修复:后端仅 `mode=video` 且非图片协议时同步可信素材;前端仅视频 mode + 视频协议(或非图片协议的 Plan BaseURL)才提交 `asset://`。后端单测已覆盖。需浏览器验收: diff --git a/docs/design/model-request-protocol-landscape.md b/docs/design/model-request-protocol-landscape.md index 4f4f5a3c3..f1f830b3e 100644 --- a/docs/design/model-request-protocol-landscape.md +++ b/docs/design/model-request-protocol-landscape.md @@ -435,6 +435,8 @@ if model name contains minimax/h3 | `openai-videos` | multipart create/task/content/cancel | | `xai-video` | xAI JSON video + reference_images | | `volcengine-ark-seedance` | Ark contents task + multimodal roles | +| `volcengine-ark-agent-plan-seedream` | Agent Plan `/api/plan/v3/images/generations` | +| `volcengine-ark-agent-plan-seedance` | Agent Plan `/api/plan/v3/contents/generations/tasks` | | `minimax-hailuo-video-v2` | MiniMax `/v2/video_generation` | | `dashscope-wan-video` | DashScope async Wan task | | `newapi-media-task-v1` | NewAPI Channel 1 包装 | diff --git a/plugin-packages/generate-catalog.mjs b/plugin-packages/generate-catalog.mjs index 3c02b3c4d..8d7838f3c 100644 --- a/plugin-packages/generate-catalog.mjs +++ b/plugin-packages/generate-catalog.mjs @@ -374,27 +374,123 @@ const arkSeedreamRatioSizes = [ ["3:2", "2496x1664"], ["2:3", "1664x2496"], ["21:9", "3024x1296"] ]; +const arkSeedreamImageBody = (optionNamespace) => ({ + model: ref("request.model"), prompt: ref("request.prompt"), + // Ark 的 size 只接受 WIDTHxHEIGHT 或 2k/3k/4k;统一层的比例值在这里换算成 2K 档像素尺寸,像素或档位值原样透传。 + size: omit({ $switch: { cases: arkSeedreamRatioSizes.map(([ratio, size]) => ({ when: eq(ref("request.aspectRatio"), ratio), then: size })), default: ref("request.aspectRatio") } }), + image: omit(conditional( + eq(len(ref("request.images")), 1), + first(map(ref("request.images"), "media", ref("media.value"))), + conditional(gt(len(ref("request.images")), 1), map(ref("request.images"), "media", ref("media.value")), null) + )), + sequential_image_generation: omit(ref(`request.providerOptions.${optionNamespace}.sequential_image_generation`)), + sequential_image_generation_options: omit(ref(`request.providerOptions.${optionNamespace}.sequential_image_generation_options`)), + watermark: coalesce(ref(`request.providerOptions.${optionNamespace}.watermark`), ref("request.watermark"), false), + seed: omit(ref(`request.providerOptions.${optionNamespace}.seed`)), + response_format: coalesce(ref(`request.providerOptions.${optionNamespace}.response_format`), "b64_json") +}); + add({ id: "volcengine-ark-seedream", providerId: "volcengine-ark-image", name: "Volcengine Ark Seedream Images", vendor: "Volcengine", capability: "image", baseUrl: "https://ark.cn-beijing.volces.com", auth: bearer, params: imageParams, - create: jsonCreate("/api/v3/images/generations", { - model: ref("request.model"), prompt: ref("request.prompt"), - // Ark 的 size 只接受 WIDTHxHEIGHT 或 2k/3k/4k;统一层的比例值在这里换算成 2K 档像素尺寸,像素或档位值原样透传。 - size: omit({ $switch: { cases: arkSeedreamRatioSizes.map(([ratio, size]) => ({ when: eq(ref("request.aspectRatio"), ratio), then: size })), default: ref("request.aspectRatio") } }), - image: omit(conditional( - eq(len(ref("request.images")), 1), - first(map(ref("request.images"), "media", ref("media.value"))), - conditional(gt(len(ref("request.images")), 1), map(ref("request.images"), "media", ref("media.value")), null) - )), - sequential_image_generation: omit(ref("request.providerOptions.volcengine-ark-image.sequential_image_generation")), - sequential_image_generation_options: omit(ref("request.providerOptions.volcengine-ark-image.sequential_image_generation_options")), - watermark: coalesce(ref("request.providerOptions.volcengine-ark-image.watermark"), ref("request.watermark"), false), - seed: omit(ref("request.providerOptions.volcengine-ark-image.seed")), - response_format: coalesce(ref("request.providerOptions.volcengine-ark-image.response_format"), "b64_json") - }), + notes: "官方 Ark 推理接入:Base URL 使用 /api/v3,API Key 来自方舟推理接入控制台,不可与 Agent Plan 专属 Key 混用。", + create: jsonCreate("/api/v3/images/generations", arkSeedreamImageBody("volcengine-ark-image")), response: { status: "succeeded", images: ref("response.data"), usage: ref("response.usage"), errorPaths: ["error.code"], messagePaths: ["error.message"] } }); +add({ + id: "volcengine-ark-agent-plan-seedream", providerId: "volcengine-ark-agent-plan-image", name: "Volcengine Ark Agent Plan Seedream Images", vendor: "Volcengine", capability: "image", + baseUrl: "https://ark.cn-beijing.volces.com", auth: bearer, params: imageParams, + notes: "Agent Plan 专属接入:请求路径为 /api/plan/v3/images/generations,必须使用 Agent Plan 控制台专属 API Key;请求体与官方 Seedream 协议一致,但凭证与额度按 AFP 套餐结算,不能与 /api/v3 官方 Key 混用。", + create: jsonCreate("/api/plan/v3/images/generations", arkSeedreamImageBody("volcengine-ark-agent-plan-image")), + response: { status: "succeeded", images: ref("response.data"), usage: ref("response.usage"), errorPaths: ["error.code"], messagePaths: ["error.message"] } +}); + +const arkSeedanceValidations = [ + { assert: { $lte: [len(ref("request.images")), 9] }, message: "Seedance 最多支持 9 张参考图片" }, + { assert: { $lte: [len(ref("request.videos")), 3] }, message: "Seedance 最多支持 3 个参考视频" }, + { assert: { $lte: [len(ref("request.audios")), 3] }, message: "Seedance 最多支持 3 个参考音频" }, + { assert: { $or: [eq(len(ref("request.audios")), 0), gt({ $add: [len(ref("request.images")), len(ref("request.videos"))] }, 0)] }, message: "Seedance 不支持纯音频或文本+音频,请同时添加参考图片或参考视频" } +]; + +const arkSeedanceParams = [ + ["model", "string", true, "model", "Ark endpoint/model ID。"], + ["prompt", "string", true, "content[type=text].text", "视频提示词。"], + ["images", "media[]", false, "content[type=image_url]", "first_frame、last_frame、reference_image 等 role 原样映射。"], + ["videos", "media[]", false, "content[type=video_url]", "reference_video。"], + ["audios", "media[]", false, "content[type=audio_url]", "reference_audio/reference_voice。"], + ["aspectRatio", "string", false, "ratio", "输出画幅。"], + ["resolution", "string", false, "resolution", "输出分辨率档位。"], + ["duration", "integer", false, "duration", "输出时长秒数。"], + ["generateAudio", "boolean", false, "generate_audio", "是否生成音频。"], + ["watermark", "boolean", false, "watermark", "是否带水印。"], + ["seed", "integer", false, "seed", "providerOptions seed。"], + ["camera_fixed", "boolean", false, "camera_fixed", "providerOptions camera_fixed。"] +]; + +const arkSeedanceBody = (optionNamespace) => ({ + model: ref("request.model"), + content: { + $concatArrays: [ + [{ type: "text", text: ref("request.prompt") }], + map(sorted(ref("request.images")), "media", { + type: "image_url", + image_url: { url: ref("media.value") }, + role: coalesce(ref("media.role"), "reference_image") + }), + map(sorted(ref("request.videos")), "media", { + type: "video_url", + video_url: { url: ref("media.value") }, + role: coalesce(ref("media.role"), "reference_video") + }), + map(sorted(ref("request.audios")), "media", { + type: "audio_url", + audio_url: { url: ref("media.value") }, + role: coalesce(ref("media.role"), "reference_audio") + }) + ] + }, + ratio: coalesce(ref("request.aspectRatio"), "16:9"), + resolution: coalesce(ref("request.resolution"), "720p"), + duration: conditional(gt(ref("request.duration"), 0), ref("request.duration"), 5), + generate_audio: ref("request.generateAudio"), + watermark: ref("request.watermark"), + seed: omit(ref(`request.providerOptions.${optionNamespace}.seed`)), + camera_fixed: omit(ref(`request.providerOptions.${optionNamespace}.camera_fixed`)) +}); + +const arkSeedanceResponse = { + taskId: coalesce(ref("response.id"), ref("response.task_id"), ref("response.data.id"), ref("taskId")), + status: coalesce(ref("response.status"), ref("response.data.status"), "pending"), + message: coalesce(ref("response.error.message"), ref("response.message"), ref("response.fail_reason")), + videos: coalesce(ref("response.content.video_url"), ref("response.video_url"), ref("response.output.video_url"), ref("response.data.video_url")), + usage: ref("response.usage"), + errorPaths: ["error.code"], + resultEphemeral: true +}; + +add({ + id: "volcengine-ark-seedance", providerId: "volcengine-ark-video", name: "Volcengine Ark Seedance", vendor: "Volcengine", capability: "video", + baseUrl: "https://ark.cn-beijing.volces.com", auth: bearer, params: arkSeedanceParams, requiresPublicMediaUrls: true, + validations: arkSeedanceValidations, + notes: "官方 Ark 推理接入:创建/查询/取消走 /api/v3/contents/generations/tasks;插件不根据图片下标推断首尾帧,role 由业务层确定。API Key 来自方舟推理接入控制台。", + create: jsonCreate("/api/v3/contents/generations/tasks", arkSeedanceBody("volcengine-ark-video")), + poll: { method: "GET", path: "/api/v3/contents/generations/tasks/{{taskId}}" }, + cancel: { method: "DELETE", path: "/api/v3/contents/generations/tasks/{{taskId}}" }, + response: arkSeedanceResponse +}); + +add({ + id: "volcengine-ark-agent-plan-seedance", providerId: "volcengine-ark-agent-plan-video", name: "Volcengine Ark Agent Plan Seedance", vendor: "Volcengine", capability: "video", + baseUrl: "https://ark.cn-beijing.volces.com", auth: bearer, params: arkSeedanceParams, requiresPublicMediaUrls: true, + validations: arkSeedanceValidations, + notes: "Agent Plan 专属接入:创建/查询/取消走 /api/plan/v3/contents/generations/tasks;请求体与官方 Seedance 协议一致,但必须使用 Agent Plan 专属 API Key 与 AFP 额度,不能与 /api/v3 官方 Key 混用。", + create: jsonCreate("/api/plan/v3/contents/generations/tasks", arkSeedanceBody("volcengine-ark-agent-plan-video")), + poll: { method: "GET", path: "/api/plan/v3/contents/generations/tasks/{{taskId}}" }, + cancel: { method: "DELETE", path: "/api/plan/v3/contents/generations/tasks/{{taskId}}" }, + response: arkSeedanceResponse +}); + // Gemini imageConfig.imageSize 只接受 1K/2K/4K;画布统一层用 1k/2k/4k 或 low/medium/high。 const geminiImageSize = omit({ $coalesce: [ diff --git a/plugin-packages/volcengine-ark-agent-plan-seedance.yingce-plugin b/plugin-packages/volcengine-ark-agent-plan-seedance.yingce-plugin new file mode 100644 index 000000000..f234239ea Binary files /dev/null and b/plugin-packages/volcengine-ark-agent-plan-seedance.yingce-plugin differ diff --git a/plugin-packages/volcengine-ark-agent-plan-seedance/README.md b/plugin-packages/volcengine-ark-agent-plan-seedance/README.md new file mode 100644 index 000000000..5c0da702e --- /dev/null +++ b/plugin-packages/volcengine-ark-agent-plan-seedance/README.md @@ -0,0 +1,5 @@ +# Volcengine Ark Agent Plan Seedance + +该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-agent-plan-seedance.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。 + +完整接口见 [docs/interface.md](docs/interface.md)。 diff --git a/plugin-packages/volcengine-ark-agent-plan-seedance/docs/interface.md b/plugin-packages/volcengine-ark-agent-plan-seedance/docs/interface.md new file mode 100644 index 000000000..8e4f49c6c --- /dev/null +++ b/plugin-packages/volcengine-ark-agent-plan-seedance/docs/interface.md @@ -0,0 +1,527 @@ +# Volcengine Ark Agent Plan Seedance 接口字段 + +## 协议身份 + +- 插件 ID:`volcengine-ark-agent-plan-seedance`。 +- Provider ID:`volcengine-ark-agent-plan-video`。 +- 能力:`video`。 +- 默认 Base URL:`https://ark.cn-beijing.volces.com`。 +- 鉴权驱动:`bearer`。 +- 创建:`POST /api/plan/v3/contents/generations/tasks`。 +- 查询:`GET /api/plan/v3/contents/generations/tasks/{{taskId}}`。 +- 取消:`DELETE /api/plan/v3/contents/generations/tasks/{{taskId}}`。 + +## 配置字段 + +| 字段 | 类型 | 必填 | 含义 | +| --- | --- | --- | --- | +| `apiKey` | secret | 是 | API Key | + +## 统一字段映射 + +| 统一字段 | 类型 | 必填 | 上游映射 | 说明 | +| --- | --- | --- | --- | --- | +| `model` | string | 是 | `model` | Ark endpoint/model ID。 | +| `prompt` | string | 是 | `content[type=text].text` | 视频提示词。 | +| `images` | media[] | 否 | `content[type=image_url]` | first_frame、last_frame、reference_image 等 role 原样映射。 | +| `videos` | media[] | 否 | `content[type=video_url]` | reference_video。 | +| `audios` | media[] | 否 | `content[type=audio_url]` | reference_audio/reference_voice。 | +| `aspectRatio` | string | 否 | `ratio` | 输出画幅。 | +| `resolution` | string | 否 | `resolution` | 输出分辨率档位。 | +| `duration` | integer | 否 | `duration` | 输出时长秒数。 | +| `generateAudio` | boolean | 否 | `generate_audio` | 是否生成音频。 | +| `watermark` | boolean | 否 | `watermark` | 是否带水印。 | +| `seed` | integer | 否 | `seed` | providerOptions seed。 | +| `camera_fixed` | boolean | 否 | `camera_fixed` | providerOptions camera_fixed。 | + +## 上游请求模板逐字段清单 + +下表由插件请求模板生成,覆盖 body、query、headers 和 multipart 文件声明中的每个字段。 + +| 上游位置 | 值或转换表达式 | +| --- | --- | +| `create.method` | `"POST"` | +| `create.path` | `"/api/plan/v3/contents/generations/tasks"` | +| `create.contentType` | `"application/json"` | +| `create.body.model` | `{"$ref":"request.model"}` | +| `create.body.content` | `{"$concatArrays":[[{"type":"text","text":{"$ref":"request.prompt"}}],{"$map":{"from":{"$sortByOrder":{"$ref":"request.images"}},"as":"media","in":{"type":"image_url","image_url":{"url":{"$ref":"media.value"}},"role":{"$coalesce":[{"$ref":"media.role"},"reference_image"]}}}},{"$map":{"from":{"$sortByOrder":{"$ref":"request.videos"}},"as":"media","in":{"type":"video_url","video_url":{"url":{"$ref":"media.value"}},"role":{"$coalesce":[{"$ref":"media.role"},"reference_video"]}}}},{"$map":{"from":{"$sortByOrder":{"$ref":"request.audios"}},"as":"media","in":{"type":"audio_url","audio_url":{"url":{"$ref":"media.value"}},"role":{"$coalesce":[{"$ref":"media.role"},"reference_audio"]}}}}]}` | +| `create.body.ratio` | `{"$coalesce":[{"$ref":"request.aspectRatio"},"16:9"]}` | +| `create.body.resolution` | `{"$coalesce":[{"$ref":"request.resolution"},"720p"]}` | +| `create.body.duration` | `{"$if":{"condition":{"$gt":[{"$ref":"request.duration"},0]},"then":{"$ref":"request.duration"},"else":5}}` | +| `create.body.generate_audio` | `{"$ref":"request.generateAudio"}` | +| `create.body.watermark` | `{"$ref":"request.watermark"}` | +| `create.body.seed` | `{"$omitEmpty":{"$ref":"request.providerOptions.volcengine-ark-agent-plan-video.seed"}}` | +| `create.body.camera_fixed` | `{"$omitEmpty":{"$ref":"request.providerOptions.volcengine-ark-agent-plan-video.camera_fixed"}}` | +| `poll.method` | `"GET"` | +| `poll.path` | `"/api/plan/v3/contents/generations/tasks/{{taskId}}"` | +| `poll.contentType` | `"application/json"` | +| `cancel.method` | `"DELETE"` | +| `cancel.path` | `"/api/plan/v3/contents/generations/tasks/{{taskId}}"` | +| `cancel.contentType` | `"application/json"` | + +## Provider 扩展键 + +- `providerOptions.volcengine-ark-agent-plan-video.camera_fixed` +- `providerOptions.volcengine-ark-agent-plan-video.seed` + +动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。 + +## 响应映射逐字段清单 + +| 映射位置 | 上游路径或转换表达式 | +| --- | --- | +| `response.taskId` | `{"$coalesce":[{"$ref":"response.id"},{"$ref":"response.task_id"},{"$ref":"response.data.id"},{"$ref":"taskId"}]}` | +| `response.status` | `{"$coalesce":[{"$ref":"response.status"},{"$ref":"response.data.status"},"pending"]}` | +| `response.message` | `{"$coalesce":[{"$ref":"response.error.message"},{"$ref":"response.message"},{"$ref":"response.fail_reason"}]}` | +| `response.videos` | `{"$coalesce":[{"$ref":"response.content.video_url"},{"$ref":"response.video_url"},{"$ref":"response.output.video_url"},{"$ref":"response.data.video_url"}]}` | +| `response.usage` | `{"$ref":"response.usage"}` | +| `response.errorPaths[0]` | `"error.code"` | +| `response.resultEphemeral` | `true` | + +## 响应与错误 + +插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。 + +## 兼容边界 + +Agent Plan 专属接入:创建/查询/取消走 /api/plan/v3/contents/generations/tasks;请求体与官方 Seedance 协议一致,但必须使用 Agent Plan 专属 API Key 与 AFP 额度,不能与 /api/v3 官方 Key 混用。 + + +## Manifest 完整接口定义 + +以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。 + +```json +{ + "apiVersion": "yingce.plugin/v2", + "id": "volcengine-ark-agent-plan-seedance", + "name": "Volcengine Ark Agent Plan Seedance", + "version": "2.0.0", + "author": "Volcengine / 影策", + "description": "Volcengine Ark Agent Plan Seedance 独立请求协议插件。", + "documentation": "<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>", + "permissions": [ + "generation.run", + "media.read" + ], + "configuration": { + "fields": [ + { + "name": "apiKey", + "type": "secret", + "label": "API Key", + "required": true + } + ] + }, + "contributes": { + "providers": [ + { + "id": "volcengine-ark-agent-plan-video", + "label": "Volcengine Ark Agent Plan Seedance", + "capabilities": [ + "video" + ], + "scopes": [ + "admin.system-channel", + "user.custom-channel", + "canvas", + "creation", + "agent" + ], + "baseUrl": "https://ark.cn-beijing.volces.com", + "requiresPublicMediaUrls": true, + "auth": { + "type": "bearer", + "field": "apiKey" + }, + "parameters": [ + { + "name": "model", + "type": "string", + "required": true, + "mapping": "model", + "description": "Ark endpoint/model ID。" + }, + { + "name": "prompt", + "type": "string", + "required": true, + "mapping": "content[type=text].text", + "description": "视频提示词。" + }, + { + "name": "images", + "type": "media[]", + "required": false, + "mapping": "content[type=image_url]", + "description": "first_frame、last_frame、reference_image 等 role 原样映射。" + }, + { + "name": "videos", + "type": "media[]", + "required": false, + "mapping": "content[type=video_url]", + "description": "reference_video。" + }, + { + "name": "audios", + "type": "media[]", + "required": false, + "mapping": "content[type=audio_url]", + "description": "reference_audio/reference_voice。" + }, + { + "name": "aspectRatio", + "type": "string", + "required": false, + "mapping": "ratio", + "description": "输出画幅。" + }, + { + "name": "resolution", + "type": "string", + "required": false, + "mapping": "resolution", + "description": "输出分辨率档位。" + }, + { + "name": "duration", + "type": "integer", + "required": false, + "mapping": "duration", + "description": "输出时长秒数。" + }, + { + "name": "generateAudio", + "type": "boolean", + "required": false, + "mapping": "generate_audio", + "description": "是否生成音频。" + }, + { + "name": "watermark", + "type": "boolean", + "required": false, + "mapping": "watermark", + "description": "是否带水印。" + }, + { + "name": "seed", + "type": "integer", + "required": false, + "mapping": "seed", + "description": "providerOptions seed。" + }, + { + "name": "camera_fixed", + "type": "boolean", + "required": false, + "mapping": "camera_fixed", + "description": "providerOptions camera_fixed。" + } + ], + "validations": [ + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.images" + } + }, + 9 + ] + }, + "message": "Seedance 最多支持 9 张参考图片" + }, + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.videos" + } + }, + 3 + ] + }, + "message": "Seedance 最多支持 3 个参考视频" + }, + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.audios" + } + }, + 3 + ] + }, + "message": "Seedance 最多支持 3 个参考音频" + }, + { + "assert": { + "$or": [ + { + "$eq": [ + { + "$len": { + "$ref": "request.audios" + } + }, + 0 + ] + }, + { + "$gt": [ + { + "$add": [ + { + "$len": { + "$ref": "request.images" + } + }, + { + "$len": { + "$ref": "request.videos" + } + } + ] + }, + 0 + ] + } + ] + }, + "message": "Seedance 不支持纯音频或文本+音频,请同时添加参考图片或参考视频" + } + ], + "create": { + "method": "POST", + "path": "/api/plan/v3/contents/generations/tasks", + "contentType": "application/json", + "body": { + "model": { + "$ref": "request.model" + }, + "content": { + "$concatArrays": [ + [ + { + "type": "text", + "text": { + "$ref": "request.prompt" + } + } + ], + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.images" + } + }, + "as": "media", + "in": { + "type": "image_url", + "image_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_image" + ] + } + } + } + }, + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.videos" + } + }, + "as": "media", + "in": { + "type": "video_url", + "video_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_video" + ] + } + } + } + }, + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.audios" + } + }, + "as": "media", + "in": { + "type": "audio_url", + "audio_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_audio" + ] + } + } + } + } + ] + }, + "ratio": { + "$coalesce": [ + { + "$ref": "request.aspectRatio" + }, + "16:9" + ] + }, + "resolution": { + "$coalesce": [ + { + "$ref": "request.resolution" + }, + "720p" + ] + }, + "duration": { + "$if": { + "condition": { + "$gt": [ + { + "$ref": "request.duration" + }, + 0 + ] + }, + "then": { + "$ref": "request.duration" + }, + "else": 5 + } + }, + "generate_audio": { + "$ref": "request.generateAudio" + }, + "watermark": { + "$ref": "request.watermark" + }, + "seed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-video.seed" + } + }, + "camera_fixed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-video.camera_fixed" + } + } + } + }, + "poll": { + "method": "GET", + "path": "/api/plan/v3/contents/generations/tasks/{{taskId}}" + }, + "cancel": { + "method": "DELETE", + "path": "/api/plan/v3/contents/generations/tasks/{{taskId}}" + }, + "response": { + "taskId": { + "$coalesce": [ + { + "$ref": "response.id" + }, + { + "$ref": "response.task_id" + }, + { + "$ref": "response.data.id" + }, + { + "$ref": "taskId" + } + ] + }, + "status": { + "$coalesce": [ + { + "$ref": "response.status" + }, + { + "$ref": "response.data.status" + }, + "pending" + ] + }, + "message": { + "$coalesce": [ + { + "$ref": "response.error.message" + }, + { + "$ref": "response.message" + }, + { + "$ref": "response.fail_reason" + } + ] + }, + "videos": { + "$coalesce": [ + { + "$ref": "response.content.video_url" + }, + { + "$ref": "response.video_url" + }, + { + "$ref": "response.output.video_url" + }, + { + "$ref": "response.data.video_url" + } + ] + }, + "usage": { + "$ref": "response.usage" + }, + "errorPaths": [ + "error.code" + ], + "resultEphemeral": true + } + } + ] + } +} +``` + diff --git a/plugin-packages/volcengine-ark-agent-plan-seedance/manifest.json b/plugin-packages/volcengine-ark-agent-plan-seedance/manifest.json new file mode 100644 index 000000000..90c6d4348 --- /dev/null +++ b/plugin-packages/volcengine-ark-agent-plan-seedance/manifest.json @@ -0,0 +1,431 @@ +{ + "apiVersion": "yingce.plugin/v2", + "id": "volcengine-ark-agent-plan-seedance", + "name": "Volcengine Ark Agent Plan Seedance", + "version": "2.0.0", + "author": "Volcengine / 影策", + "description": "Volcengine Ark Agent Plan Seedance 独立请求协议插件。", + "documentation": "# Volcengine Ark Agent Plan Seedance\n\n该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-agent-plan-seedance.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。\n\n完整接口见 [docs/interface.md](docs/interface.md)。\n\n---\n\n# Volcengine Ark Agent Plan Seedance 接口字段\n\n## 协议身份\n\n- 插件 ID:`volcengine-ark-agent-plan-seedance`。\n- Provider ID:`volcengine-ark-agent-plan-video`。\n- 能力:`video`。\n- 默认 Base URL:`https://ark.cn-beijing.volces.com`。\n- 鉴权驱动:`bearer`。\n- 创建:`POST /api/plan/v3/contents/generations/tasks`。\n- 查询:`GET /api/plan/v3/contents/generations/tasks/{{taskId}}`。\n- 取消:`DELETE /api/plan/v3/contents/generations/tasks/{{taskId}}`。\n\n## 配置字段\n\n| 字段 | 类型 | 必填 | 含义 |\n| --- | --- | --- | --- |\n| `apiKey` | secret | 是 | API Key |\n\n## 统一字段映射\n\n| 统一字段 | 类型 | 必填 | 上游映射 | 说明 |\n| --- | --- | --- | --- | --- |\n| `model` | string | 是 | `model` | Ark endpoint/model ID。 |\n| `prompt` | string | 是 | `content[type=text].text` | 视频提示词。 |\n| `images` | media[] | 否 | `content[type=image_url]` | first_frame、last_frame、reference_image 等 role 原样映射。 |\n| `videos` | media[] | 否 | `content[type=video_url]` | reference_video。 |\n| `audios` | media[] | 否 | `content[type=audio_url]` | reference_audio/reference_voice。 |\n| `aspectRatio` | string | 否 | `ratio` | 输出画幅。 |\n| `resolution` | string | 否 | `resolution` | 输出分辨率档位。 |\n| `duration` | integer | 否 | `duration` | 输出时长秒数。 |\n| `generateAudio` | boolean | 否 | `generate_audio` | 是否生成音频。 |\n| `watermark` | boolean | 否 | `watermark` | 是否带水印。 |\n| `seed` | integer | 否 | `seed` | providerOptions seed。 |\n| `camera_fixed` | boolean | 否 | `camera_fixed` | providerOptions camera_fixed。 |\n\n## 上游请求模板逐字段清单\n\n下表由插件请求模板生成,覆盖 body、query、headers 和 multipart 文件声明中的每个字段。\n\n| 上游位置 | 值或转换表达式 |\n| --- | --- |\n| `create.method` | `\"POST\"` |\n| `create.path` | `\"/api/plan/v3/contents/generations/tasks\"` |\n| `create.contentType` | `\"application/json\"` |\n| `create.body.model` | `{\"$ref\":\"request.model\"}` |\n| `create.body.content` | `{\"$concatArrays\":[[{\"type\":\"text\",\"text\":{\"$ref\":\"request.prompt\"}}],{\"$map\":{\"from\":{\"$sortByOrder\":{\"$ref\":\"request.images\"}},\"as\":\"media\",\"in\":{\"type\":\"image_url\",\"image_url\":{\"url\":{\"$ref\":\"media.value\"}},\"role\":{\"$coalesce\":[{\"$ref\":\"media.role\"},\"reference_image\"]}}}},{\"$map\":{\"from\":{\"$sortByOrder\":{\"$ref\":\"request.videos\"}},\"as\":\"media\",\"in\":{\"type\":\"video_url\",\"video_url\":{\"url\":{\"$ref\":\"media.value\"}},\"role\":{\"$coalesce\":[{\"$ref\":\"media.role\"},\"reference_video\"]}}}},{\"$map\":{\"from\":{\"$sortByOrder\":{\"$ref\":\"request.audios\"}},\"as\":\"media\",\"in\":{\"type\":\"audio_url\",\"audio_url\":{\"url\":{\"$ref\":\"media.value\"}},\"role\":{\"$coalesce\":[{\"$ref\":\"media.role\"},\"reference_audio\"]}}}}]}` |\n| `create.body.ratio` | `{\"$coalesce\":[{\"$ref\":\"request.aspectRatio\"},\"16:9\"]}` |\n| `create.body.resolution` | `{\"$coalesce\":[{\"$ref\":\"request.resolution\"},\"720p\"]}` |\n| `create.body.duration` | `{\"$if\":{\"condition\":{\"$gt\":[{\"$ref\":\"request.duration\"},0]},\"then\":{\"$ref\":\"request.duration\"},\"else\":5}}` |\n| `create.body.generate_audio` | `{\"$ref\":\"request.generateAudio\"}` |\n| `create.body.watermark` | `{\"$ref\":\"request.watermark\"}` |\n| `create.body.seed` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-agent-plan-video.seed\"}}` |\n| `create.body.camera_fixed` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-agent-plan-video.camera_fixed\"}}` |\n| `poll.method` | `\"GET\"` |\n| `poll.path` | `\"/api/plan/v3/contents/generations/tasks/{{taskId}}\"` |\n| `poll.contentType` | `\"application/json\"` |\n| `cancel.method` | `\"DELETE\"` |\n| `cancel.path` | `\"/api/plan/v3/contents/generations/tasks/{{taskId}}\"` |\n| `cancel.contentType` | `\"application/json\"` |\n\n## Provider 扩展键\n\n- `providerOptions.volcengine-ark-agent-plan-video.camera_fixed`\n- `providerOptions.volcengine-ark-agent-plan-video.seed`\n\n动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。\n\n## 响应映射逐字段清单\n\n| 映射位置 | 上游路径或转换表达式 |\n| --- | --- |\n| `response.taskId` | `{\"$coalesce\":[{\"$ref\":\"response.id\"},{\"$ref\":\"response.task_id\"},{\"$ref\":\"response.data.id\"},{\"$ref\":\"taskId\"}]}` |\n| `response.status` | `{\"$coalesce\":[{\"$ref\":\"response.status\"},{\"$ref\":\"response.data.status\"},\"pending\"]}` |\n| `response.message` | `{\"$coalesce\":[{\"$ref\":\"response.error.message\"},{\"$ref\":\"response.message\"},{\"$ref\":\"response.fail_reason\"}]}` |\n| `response.videos` | `{\"$coalesce\":[{\"$ref\":\"response.content.video_url\"},{\"$ref\":\"response.video_url\"},{\"$ref\":\"response.output.video_url\"},{\"$ref\":\"response.data.video_url\"}]}` |\n| `response.usage` | `{\"$ref\":\"response.usage\"}` |\n| `response.errorPaths[0]` | `\"error.code\"` |\n| `response.resultEphemeral` | `true` |\n\n## 响应与错误\n\n插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。\n\n## 兼容边界\n\nAgent Plan 专属接入:创建/查询/取消走 /api/plan/v3/contents/generations/tasks;请求体与官方 Seedance 协议一致,但必须使用 Agent Plan 专属 API Key 与 AFP 额度,不能与 /api/v3 官方 Key 混用。\n\n\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v2\",\n \"id\": \"volcengine-ark-agent-plan-seedance\",\n \"name\": \"Volcengine Ark Agent Plan Seedance\",\n \"version\": \"2.0.0\",\n \"author\": \"Volcengine / 影策\",\n \"description\": \"Volcengine Ark Agent Plan Seedance 独立请求协议插件。\",\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\",\n \"permissions\": [\n \"generation.run\",\n \"media.read\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"apiKey\",\n \"type\": \"secret\",\n \"label\": \"API Key\",\n \"required\": true\n }\n ]\n },\n \"contributes\": {\n \"providers\": [\n {\n \"id\": \"volcengine-ark-agent-plan-video\",\n \"label\": \"Volcengine Ark Agent Plan Seedance\",\n \"capabilities\": [\n \"video\"\n ],\n \"scopes\": [\n \"admin.system-channel\",\n \"user.custom-channel\",\n \"canvas\",\n \"creation\",\n \"agent\"\n ],\n \"baseUrl\": \"https://ark.cn-beijing.volces.com\",\n \"requiresPublicMediaUrls\": true,\n \"auth\": {\n \"type\": \"bearer\",\n \"field\": \"apiKey\"\n },\n \"parameters\": [\n {\n \"name\": \"model\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"model\",\n \"description\": \"Ark endpoint/model ID。\"\n },\n {\n \"name\": \"prompt\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"content[type=text].text\",\n \"description\": \"视频提示词。\"\n },\n {\n \"name\": \"images\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"content[type=image_url]\",\n \"description\": \"first_frame、last_frame、reference_image 等 role 原样映射。\"\n },\n {\n \"name\": \"videos\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"content[type=video_url]\",\n \"description\": \"reference_video。\"\n },\n {\n \"name\": \"audios\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"content[type=audio_url]\",\n \"description\": \"reference_audio/reference_voice。\"\n },\n {\n \"name\": \"aspectRatio\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"ratio\",\n \"description\": \"输出画幅。\"\n },\n {\n \"name\": \"resolution\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"resolution\",\n \"description\": \"输出分辨率档位。\"\n },\n {\n \"name\": \"duration\",\n \"type\": \"integer\",\n \"required\": false,\n \"mapping\": \"duration\",\n \"description\": \"输出时长秒数。\"\n },\n {\n \"name\": \"generateAudio\",\n \"type\": \"boolean\",\n \"required\": false,\n \"mapping\": \"generate_audio\",\n \"description\": \"是否生成音频。\"\n },\n {\n \"name\": \"watermark\",\n \"type\": \"boolean\",\n \"required\": false,\n \"mapping\": \"watermark\",\n \"description\": \"是否带水印。\"\n },\n {\n \"name\": \"seed\",\n \"type\": \"integer\",\n \"required\": false,\n \"mapping\": \"seed\",\n \"description\": \"providerOptions seed。\"\n },\n {\n \"name\": \"camera_fixed\",\n \"type\": \"boolean\",\n \"required\": false,\n \"mapping\": \"camera_fixed\",\n \"description\": \"providerOptions camera_fixed。\"\n }\n ],\n \"validations\": [\n {\n \"assert\": {\n \"$lte\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n 9\n ]\n },\n \"message\": \"Seedance 最多支持 9 张参考图片\"\n },\n {\n \"assert\": {\n \"$lte\": [\n {\n \"$len\": {\n \"$ref\": \"request.videos\"\n }\n },\n 3\n ]\n },\n \"message\": \"Seedance 最多支持 3 个参考视频\"\n },\n {\n \"assert\": {\n \"$lte\": [\n {\n \"$len\": {\n \"$ref\": \"request.audios\"\n }\n },\n 3\n ]\n },\n \"message\": \"Seedance 最多支持 3 个参考音频\"\n },\n {\n \"assert\": {\n \"$or\": [\n {\n \"$eq\": [\n {\n \"$len\": {\n \"$ref\": \"request.audios\"\n }\n },\n 0\n ]\n },\n {\n \"$gt\": [\n {\n \"$add\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n {\n \"$len\": {\n \"$ref\": \"request.videos\"\n }\n }\n ]\n },\n 0\n ]\n }\n ]\n },\n \"message\": \"Seedance 不支持纯音频或文本+音频,请同时添加参考图片或参考视频\"\n }\n ],\n \"create\": {\n \"method\": \"POST\",\n \"path\": \"/api/plan/v3/contents/generations/tasks\",\n \"contentType\": \"application/json\",\n \"body\": {\n \"model\": {\n \"$ref\": \"request.model\"\n },\n \"content\": {\n \"$concatArrays\": [\n [\n {\n \"type\": \"text\",\n \"text\": {\n \"$ref\": \"request.prompt\"\n }\n }\n ],\n {\n \"$map\": {\n \"from\": {\n \"$sortByOrder\": {\n \"$ref\": \"request.images\"\n }\n },\n \"as\": \"media\",\n \"in\": {\n \"type\": \"image_url\",\n \"image_url\": {\n \"url\": {\n \"$ref\": \"media.value\"\n }\n },\n \"role\": {\n \"$coalesce\": [\n {\n \"$ref\": \"media.role\"\n },\n \"reference_image\"\n ]\n }\n }\n }\n },\n {\n \"$map\": {\n \"from\": {\n \"$sortByOrder\": {\n \"$ref\": \"request.videos\"\n }\n },\n \"as\": \"media\",\n \"in\": {\n \"type\": \"video_url\",\n \"video_url\": {\n \"url\": {\n \"$ref\": \"media.value\"\n }\n },\n \"role\": {\n \"$coalesce\": [\n {\n \"$ref\": \"media.role\"\n },\n \"reference_video\"\n ]\n }\n }\n }\n },\n {\n \"$map\": {\n \"from\": {\n \"$sortByOrder\": {\n \"$ref\": \"request.audios\"\n }\n },\n \"as\": \"media\",\n \"in\": {\n \"type\": \"audio_url\",\n \"audio_url\": {\n \"url\": {\n \"$ref\": \"media.value\"\n }\n },\n \"role\": {\n \"$coalesce\": [\n {\n \"$ref\": \"media.role\"\n },\n \"reference_audio\"\n ]\n }\n }\n }\n }\n ]\n },\n \"ratio\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"16:9\"\n ]\n },\n \"resolution\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.resolution\"\n },\n \"720p\"\n ]\n },\n \"duration\": {\n \"$if\": {\n \"condition\": {\n \"$gt\": [\n {\n \"$ref\": \"request.duration\"\n },\n 0\n ]\n },\n \"then\": {\n \"$ref\": \"request.duration\"\n },\n \"else\": 5\n }\n },\n \"generate_audio\": {\n \"$ref\": \"request.generateAudio\"\n },\n \"watermark\": {\n \"$ref\": \"request.watermark\"\n },\n \"seed\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-agent-plan-video.seed\"\n }\n },\n \"camera_fixed\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-agent-plan-video.camera_fixed\"\n }\n }\n }\n },\n \"poll\": {\n \"method\": \"GET\",\n \"path\": \"/api/plan/v3/contents/generations/tasks/{{taskId}}\"\n },\n \"cancel\": {\n \"method\": \"DELETE\",\n \"path\": \"/api/plan/v3/contents/generations/tasks/{{taskId}}\"\n },\n \"response\": {\n \"taskId\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.id\"\n },\n {\n \"$ref\": \"response.task_id\"\n },\n {\n \"$ref\": \"response.data.id\"\n },\n {\n \"$ref\": \"taskId\"\n }\n ]\n },\n \"status\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.status\"\n },\n {\n \"$ref\": \"response.data.status\"\n },\n \"pending\"\n ]\n },\n \"message\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.error.message\"\n },\n {\n \"$ref\": \"response.message\"\n },\n {\n \"$ref\": \"response.fail_reason\"\n }\n ]\n },\n \"videos\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.content.video_url\"\n },\n {\n \"$ref\": \"response.video_url\"\n },\n {\n \"$ref\": \"response.output.video_url\"\n },\n {\n \"$ref\": \"response.data.video_url\"\n }\n ]\n },\n \"usage\": {\n \"$ref\": \"response.usage\"\n },\n \"errorPaths\": [\n \"error.code\"\n ],\n \"resultEphemeral\": true\n }\n }\n ]\n }\n}\n```\n\n", + "permissions": [ + "generation.run", + "media.read" + ], + "configuration": { + "fields": [ + { + "name": "apiKey", + "type": "secret", + "label": "API Key", + "required": true + } + ] + }, + "contributes": { + "providers": [ + { + "id": "volcengine-ark-agent-plan-video", + "label": "Volcengine Ark Agent Plan Seedance", + "capabilities": [ + "video" + ], + "scopes": [ + "admin.system-channel", + "user.custom-channel", + "canvas", + "creation", + "agent" + ], + "baseUrl": "https://ark.cn-beijing.volces.com", + "requiresPublicMediaUrls": true, + "auth": { + "type": "bearer", + "field": "apiKey" + }, + "parameters": [ + { + "name": "model", + "type": "string", + "required": true, + "mapping": "model", + "description": "Ark endpoint/model ID。" + }, + { + "name": "prompt", + "type": "string", + "required": true, + "mapping": "content[type=text].text", + "description": "视频提示词。" + }, + { + "name": "images", + "type": "media[]", + "required": false, + "mapping": "content[type=image_url]", + "description": "first_frame、last_frame、reference_image 等 role 原样映射。" + }, + { + "name": "videos", + "type": "media[]", + "required": false, + "mapping": "content[type=video_url]", + "description": "reference_video。" + }, + { + "name": "audios", + "type": "media[]", + "required": false, + "mapping": "content[type=audio_url]", + "description": "reference_audio/reference_voice。" + }, + { + "name": "aspectRatio", + "type": "string", + "required": false, + "mapping": "ratio", + "description": "输出画幅。" + }, + { + "name": "resolution", + "type": "string", + "required": false, + "mapping": "resolution", + "description": "输出分辨率档位。" + }, + { + "name": "duration", + "type": "integer", + "required": false, + "mapping": "duration", + "description": "输出时长秒数。" + }, + { + "name": "generateAudio", + "type": "boolean", + "required": false, + "mapping": "generate_audio", + "description": "是否生成音频。" + }, + { + "name": "watermark", + "type": "boolean", + "required": false, + "mapping": "watermark", + "description": "是否带水印。" + }, + { + "name": "seed", + "type": "integer", + "required": false, + "mapping": "seed", + "description": "providerOptions seed。" + }, + { + "name": "camera_fixed", + "type": "boolean", + "required": false, + "mapping": "camera_fixed", + "description": "providerOptions camera_fixed。" + } + ], + "validations": [ + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.images" + } + }, + 9 + ] + }, + "message": "Seedance 最多支持 9 张参考图片" + }, + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.videos" + } + }, + 3 + ] + }, + "message": "Seedance 最多支持 3 个参考视频" + }, + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.audios" + } + }, + 3 + ] + }, + "message": "Seedance 最多支持 3 个参考音频" + }, + { + "assert": { + "$or": [ + { + "$eq": [ + { + "$len": { + "$ref": "request.audios" + } + }, + 0 + ] + }, + { + "$gt": [ + { + "$add": [ + { + "$len": { + "$ref": "request.images" + } + }, + { + "$len": { + "$ref": "request.videos" + } + } + ] + }, + 0 + ] + } + ] + }, + "message": "Seedance 不支持纯音频或文本+音频,请同时添加参考图片或参考视频" + } + ], + "create": { + "method": "POST", + "path": "/api/plan/v3/contents/generations/tasks", + "contentType": "application/json", + "body": { + "model": { + "$ref": "request.model" + }, + "content": { + "$concatArrays": [ + [ + { + "type": "text", + "text": { + "$ref": "request.prompt" + } + } + ], + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.images" + } + }, + "as": "media", + "in": { + "type": "image_url", + "image_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_image" + ] + } + } + } + }, + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.videos" + } + }, + "as": "media", + "in": { + "type": "video_url", + "video_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_video" + ] + } + } + } + }, + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.audios" + } + }, + "as": "media", + "in": { + "type": "audio_url", + "audio_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_audio" + ] + } + } + } + } + ] + }, + "ratio": { + "$coalesce": [ + { + "$ref": "request.aspectRatio" + }, + "16:9" + ] + }, + "resolution": { + "$coalesce": [ + { + "$ref": "request.resolution" + }, + "720p" + ] + }, + "duration": { + "$if": { + "condition": { + "$gt": [ + { + "$ref": "request.duration" + }, + 0 + ] + }, + "then": { + "$ref": "request.duration" + }, + "else": 5 + } + }, + "generate_audio": { + "$ref": "request.generateAudio" + }, + "watermark": { + "$ref": "request.watermark" + }, + "seed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-video.seed" + } + }, + "camera_fixed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-video.camera_fixed" + } + } + } + }, + "poll": { + "method": "GET", + "path": "/api/plan/v3/contents/generations/tasks/{{taskId}}" + }, + "cancel": { + "method": "DELETE", + "path": "/api/plan/v3/contents/generations/tasks/{{taskId}}" + }, + "response": { + "taskId": { + "$coalesce": [ + { + "$ref": "response.id" + }, + { + "$ref": "response.task_id" + }, + { + "$ref": "response.data.id" + }, + { + "$ref": "taskId" + } + ] + }, + "status": { + "$coalesce": [ + { + "$ref": "response.status" + }, + { + "$ref": "response.data.status" + }, + "pending" + ] + }, + "message": { + "$coalesce": [ + { + "$ref": "response.error.message" + }, + { + "$ref": "response.message" + }, + { + "$ref": "response.fail_reason" + } + ] + }, + "videos": { + "$coalesce": [ + { + "$ref": "response.content.video_url" + }, + { + "$ref": "response.video_url" + }, + { + "$ref": "response.output.video_url" + }, + { + "$ref": "response.data.video_url" + } + ] + }, + "usage": { + "$ref": "response.usage" + }, + "errorPaths": [ + "error.code" + ], + "resultEphemeral": true + } + } + ] + } +} diff --git a/plugin-packages/volcengine-ark-agent-plan-seedream.yingce-plugin b/plugin-packages/volcengine-ark-agent-plan-seedream.yingce-plugin new file mode 100644 index 000000000..9c6c95b36 Binary files /dev/null and b/plugin-packages/volcengine-ark-agent-plan-seedream.yingce-plugin differ diff --git a/plugin-packages/volcengine-ark-agent-plan-seedream/README.md b/plugin-packages/volcengine-ark-agent-plan-seedream/README.md new file mode 100644 index 000000000..8d6f789c6 --- /dev/null +++ b/plugin-packages/volcengine-ark-agent-plan-seedream/README.md @@ -0,0 +1,5 @@ +# Volcengine Ark Agent Plan Seedream Images + +该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-agent-plan-seedream.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。 + +完整接口见 [docs/interface.md](docs/interface.md)。 diff --git a/plugin-packages/volcengine-ark-agent-plan-seedream/docs/interface.md b/plugin-packages/volcengine-ark-agent-plan-seedream/docs/interface.md new file mode 100644 index 000000000..c207d9653 --- /dev/null +++ b/plugin-packages/volcengine-ark-agent-plan-seedream/docs/interface.md @@ -0,0 +1,418 @@ +# Volcengine Ark Agent Plan Seedream Images 接口字段 + +## 协议身份 + +- 插件 ID:`volcengine-ark-agent-plan-seedream`。 +- Provider ID:`volcengine-ark-agent-plan-image`。 +- 能力:`image`。 +- 默认 Base URL:`https://ark.cn-beijing.volces.com`。 +- 鉴权驱动:`bearer`。 +- 创建:`POST /api/plan/v3/images/generations`。 +- 生命周期:同步响应。 + +## 配置字段 + +| 字段 | 类型 | 必填 | 含义 | +| --- | --- | --- | --- | +| `apiKey` | secret | 是 | API Key | + +## 统一字段映射 + +| 统一字段 | 类型 | 必填 | 上游映射 | 说明 | +| --- | --- | --- | --- | --- | +| `model` | string | 是 | `model` | 图片模型 ID。 | +| `prompt` | string | 是 | `prompt` | 图片提示词。 | +| `images` | media[] | 否 | `provider image/reference fields` | 参考图或编辑源图,role 由业务层确定。 | +| `imageCount` | integer | 否 | `n/sample_count` | 输出数量。 | +| `aspectRatio` | string | 否 | `size/aspect_ratio` | 比例或尺寸,语义按协议说明。 | +| `resolution` | string | 否 | `resolution/imageSize` | 分辨率档位。 | +| `quality` | string | 否 | `quality` | 质量档位。 | +| `providerOptions` | object | 否 | `provider-specific fields` | 插件命名空间内的厂商扩展字段。 | + +## 上游请求模板逐字段清单 + +下表由插件请求模板生成,覆盖 body、query、headers 和 multipart 文件声明中的每个字段。 + +| 上游位置 | 值或转换表达式 | +| --- | --- | +| `create.method` | `"POST"` | +| `create.path` | `"/api/plan/v3/images/generations"` | +| `create.contentType` | `"application/json"` | +| `create.body.model` | `{"$ref":"request.model"}` | +| `create.body.prompt` | `{"$ref":"request.prompt"}` | +| `create.body.size` | `{"$omitEmpty":{"$switch":{"cases":[{"when":{"$eq":[{"$ref":"request.aspectRatio"},"auto"]},"then":"2k"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"1:1"]},"then":"2048x2048"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"4:3"]},"then":"2304x1728"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"3:4"]},"then":"1728x2304"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"16:9"]},"then":"2560x1440"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"9:16"]},"then":"1440x2560"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"3:2"]},"then":"2496x1664"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"2:3"]},"then":"1664x2496"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"21:9"]},"then":"3024x1296"}],"default":{"$ref":"request.aspectRatio"}}}}` | +| `create.body.image` | `{"$omitEmpty":{"$if":{"condition":{"$eq":[{"$len":{"$ref":"request.images"}},1]},"then":{"$first":{"$map":{"from":{"$ref":"request.images"},"as":"media","in":{"$ref":"media.value"}}}},"else":{"$if":{"condition":{"$gt":[{"$len":{"$ref":"request.images"}},1]},"then":{"$map":{"from":{"$ref":"request.images"},"as":"media","in":{"$ref":"media.value"}}},"else":null}}}}}` | +| `create.body.sequential_image_generation` | `{"$omitEmpty":{"$ref":"request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation"}}` | +| `create.body.sequential_image_generation_options` | `{"$omitEmpty":{"$ref":"request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation_options"}}` | +| `create.body.watermark` | `{"$coalesce":[{"$ref":"request.providerOptions.volcengine-ark-agent-plan-image.watermark"},{"$ref":"request.watermark"},false]}` | +| `create.body.seed` | `{"$omitEmpty":{"$ref":"request.providerOptions.volcengine-ark-agent-plan-image.seed"}}` | +| `create.body.response_format` | `{"$coalesce":[{"$ref":"request.providerOptions.volcengine-ark-agent-plan-image.response_format"},"b64_json"]}` | + +## Provider 扩展键 + +- `providerOptions.volcengine-ark-agent-plan-image.response_format` +- `providerOptions.volcengine-ark-agent-plan-image.seed` +- `providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation` +- `providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation_options` +- `providerOptions.volcengine-ark-agent-plan-image.watermark` + +动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。 + +## 响应映射逐字段清单 + +| 映射位置 | 上游路径或转换表达式 | +| --- | --- | +| `response.status` | `"succeeded"` | +| `response.images` | `{"$ref":"response.data"}` | +| `response.usage` | `{"$ref":"response.usage"}` | +| `response.errorPaths[0]` | `"error.code"` | +| `response.messagePaths[0]` | `"error.message"` | + +## 响应与错误 + +插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。 + +## 兼容边界 + +Agent Plan 专属接入:请求路径为 /api/plan/v3/images/generations,必须使用 Agent Plan 控制台专属 API Key;请求体与官方 Seedream 协议一致,但凭证与额度按 AFP 套餐结算,不能与 /api/v3 官方 Key 混用。 + + +## Manifest 完整接口定义 + +以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。 + +```json +{ + "apiVersion": "yingce.plugin/v2", + "id": "volcengine-ark-agent-plan-seedream", + "name": "Volcengine Ark Agent Plan Seedream Images", + "version": "2.0.0", + "author": "Volcengine / 影策", + "description": "Volcengine Ark Agent Plan Seedream Images 独立请求协议插件。", + "documentation": "<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>", + "permissions": [ + "generation.run", + "media.read" + ], + "configuration": { + "fields": [ + { + "name": "apiKey", + "type": "secret", + "label": "API Key", + "required": true + } + ] + }, + "contributes": { + "providers": [ + { + "id": "volcengine-ark-agent-plan-image", + "label": "Volcengine Ark Agent Plan Seedream Images", + "capabilities": [ + "image" + ], + "scopes": [ + "admin.system-channel", + "user.custom-channel", + "canvas", + "creation", + "agent" + ], + "baseUrl": "https://ark.cn-beijing.volces.com", + "requiresPublicMediaUrls": false, + "auth": { + "type": "bearer", + "field": "apiKey" + }, + "parameters": [ + { + "name": "model", + "type": "string", + "required": true, + "mapping": "model", + "description": "图片模型 ID。" + }, + { + "name": "prompt", + "type": "string", + "required": true, + "mapping": "prompt", + "description": "图片提示词。" + }, + { + "name": "images", + "type": "media[]", + "required": false, + "mapping": "provider image/reference fields", + "description": "参考图或编辑源图,role 由业务层确定。" + }, + { + "name": "imageCount", + "type": "integer", + "required": false, + "mapping": "n/sample_count", + "description": "输出数量。" + }, + { + "name": "aspectRatio", + "type": "string", + "required": false, + "mapping": "size/aspect_ratio", + "description": "比例或尺寸,语义按协议说明。" + }, + { + "name": "resolution", + "type": "string", + "required": false, + "mapping": "resolution/imageSize", + "description": "分辨率档位。" + }, + { + "name": "quality", + "type": "string", + "required": false, + "mapping": "quality", + "description": "质量档位。" + }, + { + "name": "providerOptions", + "type": "object", + "required": false, + "mapping": "provider-specific fields", + "description": "插件命名空间内的厂商扩展字段。" + } + ], + "create": { + "method": "POST", + "path": "/api/plan/v3/images/generations", + "contentType": "application/json", + "body": { + "model": { + "$ref": "request.model" + }, + "prompt": { + "$ref": "request.prompt" + }, + "size": { + "$omitEmpty": { + "$switch": { + "cases": [ + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "auto" + ] + }, + "then": "2k" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1:1" + ] + }, + "then": "2048x2048" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "4:3" + ] + }, + "then": "2304x1728" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "3:4" + ] + }, + "then": "1728x2304" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "16:9" + ] + }, + "then": "2560x1440" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "9:16" + ] + }, + "then": "1440x2560" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "3:2" + ] + }, + "then": "2496x1664" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "2:3" + ] + }, + "then": "1664x2496" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "21:9" + ] + }, + "then": "3024x1296" + } + ], + "default": { + "$ref": "request.aspectRatio" + } + } + } + }, + "image": { + "$omitEmpty": { + "$if": { + "condition": { + "$eq": [ + { + "$len": { + "$ref": "request.images" + } + }, + 1 + ] + }, + "then": { + "$first": { + "$map": { + "from": { + "$ref": "request.images" + }, + "as": "media", + "in": { + "$ref": "media.value" + } + } + } + }, + "else": { + "$if": { + "condition": { + "$gt": [ + { + "$len": { + "$ref": "request.images" + } + }, + 1 + ] + }, + "then": { + "$map": { + "from": { + "$ref": "request.images" + }, + "as": "media", + "in": { + "$ref": "media.value" + } + } + }, + "else": null + } + } + } + } + }, + "sequential_image_generation": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation" + } + }, + "sequential_image_generation_options": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation_options" + } + }, + "watermark": { + "$coalesce": [ + { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.watermark" + }, + { + "$ref": "request.watermark" + }, + false + ] + }, + "seed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.seed" + } + }, + "response_format": { + "$coalesce": [ + { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.response_format" + }, + "b64_json" + ] + } + } + }, + "response": { + "status": "succeeded", + "images": { + "$ref": "response.data" + }, + "usage": { + "$ref": "response.usage" + }, + "errorPaths": [ + "error.code" + ], + "messagePaths": [ + "error.message" + ] + } + } + ] + } +} +``` + diff --git a/plugin-packages/volcengine-ark-agent-plan-seedream/manifest.json b/plugin-packages/volcengine-ark-agent-plan-seedream/manifest.json new file mode 100644 index 000000000..3626d36a0 --- /dev/null +++ b/plugin-packages/volcengine-ark-agent-plan-seedream/manifest.json @@ -0,0 +1,332 @@ +{ + "apiVersion": "yingce.plugin/v2", + "id": "volcengine-ark-agent-plan-seedream", + "name": "Volcengine Ark Agent Plan Seedream Images", + "version": "2.0.0", + "author": "Volcengine / 影策", + "description": "Volcengine Ark Agent Plan Seedream Images 独立请求协议插件。", + "documentation": "# Volcengine Ark Agent Plan Seedream Images\n\n该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-agent-plan-seedream.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。\n\n完整接口见 [docs/interface.md](docs/interface.md)。\n\n---\n\n# Volcengine Ark Agent Plan Seedream Images 接口字段\n\n## 协议身份\n\n- 插件 ID:`volcengine-ark-agent-plan-seedream`。\n- Provider ID:`volcengine-ark-agent-plan-image`。\n- 能力:`image`。\n- 默认 Base URL:`https://ark.cn-beijing.volces.com`。\n- 鉴权驱动:`bearer`。\n- 创建:`POST /api/plan/v3/images/generations`。\n- 生命周期:同步响应。\n\n## 配置字段\n\n| 字段 | 类型 | 必填 | 含义 |\n| --- | --- | --- | --- |\n| `apiKey` | secret | 是 | API Key |\n\n## 统一字段映射\n\n| 统一字段 | 类型 | 必填 | 上游映射 | 说明 |\n| --- | --- | --- | --- | --- |\n| `model` | string | 是 | `model` | 图片模型 ID。 |\n| `prompt` | string | 是 | `prompt` | 图片提示词。 |\n| `images` | media[] | 否 | `provider image/reference fields` | 参考图或编辑源图,role 由业务层确定。 |\n| `imageCount` | integer | 否 | `n/sample_count` | 输出数量。 |\n| `aspectRatio` | string | 否 | `size/aspect_ratio` | 比例或尺寸,语义按协议说明。 |\n| `resolution` | string | 否 | `resolution/imageSize` | 分辨率档位。 |\n| `quality` | string | 否 | `quality` | 质量档位。 |\n| `providerOptions` | object | 否 | `provider-specific fields` | 插件命名空间内的厂商扩展字段。 |\n\n## 上游请求模板逐字段清单\n\n下表由插件请求模板生成,覆盖 body、query、headers 和 multipart 文件声明中的每个字段。\n\n| 上游位置 | 值或转换表达式 |\n| --- | --- |\n| `create.method` | `\"POST\"` |\n| `create.path` | `\"/api/plan/v3/images/generations\"` |\n| `create.contentType` | `\"application/json\"` |\n| `create.body.model` | `{\"$ref\":\"request.model\"}` |\n| `create.body.prompt` | `{\"$ref\":\"request.prompt\"}` |\n| `create.body.size` | `{\"$omitEmpty\":{\"$switch\":{\"cases\":[{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"auto\"]},\"then\":\"2k\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1:1\"]},\"then\":\"2048x2048\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"4:3\"]},\"then\":\"2304x1728\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"3:4\"]},\"then\":\"1728x2304\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"16:9\"]},\"then\":\"2560x1440\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"9:16\"]},\"then\":\"1440x2560\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"3:2\"]},\"then\":\"2496x1664\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"2:3\"]},\"then\":\"1664x2496\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"21:9\"]},\"then\":\"3024x1296\"}],\"default\":{\"$ref\":\"request.aspectRatio\"}}}}` |\n| `create.body.image` | `{\"$omitEmpty\":{\"$if\":{\"condition\":{\"$eq\":[{\"$len\":{\"$ref\":\"request.images\"}},1]},\"then\":{\"$first\":{\"$map\":{\"from\":{\"$ref\":\"request.images\"},\"as\":\"media\",\"in\":{\"$ref\":\"media.value\"}}}},\"else\":{\"$if\":{\"condition\":{\"$gt\":[{\"$len\":{\"$ref\":\"request.images\"}},1]},\"then\":{\"$map\":{\"from\":{\"$ref\":\"request.images\"},\"as\":\"media\",\"in\":{\"$ref\":\"media.value\"}}},\"else\":null}}}}}` |\n| `create.body.sequential_image_generation` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation\"}}` |\n| `create.body.sequential_image_generation_options` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation_options\"}}` |\n| `create.body.watermark` | `{\"$coalesce\":[{\"$ref\":\"request.providerOptions.volcengine-ark-agent-plan-image.watermark\"},{\"$ref\":\"request.watermark\"},false]}` |\n| `create.body.seed` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-agent-plan-image.seed\"}}` |\n| `create.body.response_format` | `{\"$coalesce\":[{\"$ref\":\"request.providerOptions.volcengine-ark-agent-plan-image.response_format\"},\"b64_json\"]}` |\n\n## Provider 扩展键\n\n- `providerOptions.volcengine-ark-agent-plan-image.response_format`\n- `providerOptions.volcengine-ark-agent-plan-image.seed`\n- `providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation`\n- `providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation_options`\n- `providerOptions.volcengine-ark-agent-plan-image.watermark`\n\n动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。\n\n## 响应映射逐字段清单\n\n| 映射位置 | 上游路径或转换表达式 |\n| --- | --- |\n| `response.status` | `\"succeeded\"` |\n| `response.images` | `{\"$ref\":\"response.data\"}` |\n| `response.usage` | `{\"$ref\":\"response.usage\"}` |\n| `response.errorPaths[0]` | `\"error.code\"` |\n| `response.messagePaths[0]` | `\"error.message\"` |\n\n## 响应与错误\n\n插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。\n\n## 兼容边界\n\nAgent Plan 专属接入:请求路径为 /api/plan/v3/images/generations,必须使用 Agent Plan 控制台专属 API Key;请求体与官方 Seedream 协议一致,但凭证与额度按 AFP 套餐结算,不能与 /api/v3 官方 Key 混用。\n\n\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v2\",\n \"id\": \"volcengine-ark-agent-plan-seedream\",\n \"name\": \"Volcengine Ark Agent Plan Seedream Images\",\n \"version\": \"2.0.0\",\n \"author\": \"Volcengine / 影策\",\n \"description\": \"Volcengine Ark Agent Plan Seedream Images 独立请求协议插件。\",\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\",\n \"permissions\": [\n \"generation.run\",\n \"media.read\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"apiKey\",\n \"type\": \"secret\",\n \"label\": \"API Key\",\n \"required\": true\n }\n ]\n },\n \"contributes\": {\n \"providers\": [\n {\n \"id\": \"volcengine-ark-agent-plan-image\",\n \"label\": \"Volcengine Ark Agent Plan Seedream Images\",\n \"capabilities\": [\n \"image\"\n ],\n \"scopes\": [\n \"admin.system-channel\",\n \"user.custom-channel\",\n \"canvas\",\n \"creation\",\n \"agent\"\n ],\n \"baseUrl\": \"https://ark.cn-beijing.volces.com\",\n \"requiresPublicMediaUrls\": false,\n \"auth\": {\n \"type\": \"bearer\",\n \"field\": \"apiKey\"\n },\n \"parameters\": [\n {\n \"name\": \"model\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"model\",\n \"description\": \"图片模型 ID。\"\n },\n {\n \"name\": \"prompt\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"prompt\",\n \"description\": \"图片提示词。\"\n },\n {\n \"name\": \"images\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"provider image/reference fields\",\n \"description\": \"参考图或编辑源图,role 由业务层确定。\"\n },\n {\n \"name\": \"imageCount\",\n \"type\": \"integer\",\n \"required\": false,\n \"mapping\": \"n/sample_count\",\n \"description\": \"输出数量。\"\n },\n {\n \"name\": \"aspectRatio\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"size/aspect_ratio\",\n \"description\": \"比例或尺寸,语义按协议说明。\"\n },\n {\n \"name\": \"resolution\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"resolution/imageSize\",\n \"description\": \"分辨率档位。\"\n },\n {\n \"name\": \"quality\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"quality\",\n \"description\": \"质量档位。\"\n },\n {\n \"name\": \"providerOptions\",\n \"type\": \"object\",\n \"required\": false,\n \"mapping\": \"provider-specific fields\",\n \"description\": \"插件命名空间内的厂商扩展字段。\"\n }\n ],\n \"create\": {\n \"method\": \"POST\",\n \"path\": \"/api/plan/v3/images/generations\",\n \"contentType\": \"application/json\",\n \"body\": {\n \"model\": {\n \"$ref\": \"request.model\"\n },\n \"prompt\": {\n \"$ref\": \"request.prompt\"\n },\n \"size\": {\n \"$omitEmpty\": {\n \"$switch\": {\n \"cases\": [\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"auto\"\n ]\n },\n \"then\": \"2k\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1:1\"\n ]\n },\n \"then\": \"2048x2048\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"4:3\"\n ]\n },\n \"then\": \"2304x1728\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"3:4\"\n ]\n },\n \"then\": \"1728x2304\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"16:9\"\n ]\n },\n \"then\": \"2560x1440\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"9:16\"\n ]\n },\n \"then\": \"1440x2560\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"3:2\"\n ]\n },\n \"then\": \"2496x1664\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"2:3\"\n ]\n },\n \"then\": \"1664x2496\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"21:9\"\n ]\n },\n \"then\": \"3024x1296\"\n }\n ],\n \"default\": {\n \"$ref\": \"request.aspectRatio\"\n }\n }\n }\n },\n \"image\": {\n \"$omitEmpty\": {\n \"$if\": {\n \"condition\": {\n \"$eq\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n 1\n ]\n },\n \"then\": {\n \"$first\": {\n \"$map\": {\n \"from\": {\n \"$ref\": \"request.images\"\n },\n \"as\": \"media\",\n \"in\": {\n \"$ref\": \"media.value\"\n }\n }\n }\n },\n \"else\": {\n \"$if\": {\n \"condition\": {\n \"$gt\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n 1\n ]\n },\n \"then\": {\n \"$map\": {\n \"from\": {\n \"$ref\": \"request.images\"\n },\n \"as\": \"media\",\n \"in\": {\n \"$ref\": \"media.value\"\n }\n }\n },\n \"else\": null\n }\n }\n }\n }\n },\n \"sequential_image_generation\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation\"\n }\n },\n \"sequential_image_generation_options\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation_options\"\n }\n },\n \"watermark\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.providerOptions.volcengine-ark-agent-plan-image.watermark\"\n },\n {\n \"$ref\": \"request.watermark\"\n },\n false\n ]\n },\n \"seed\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-agent-plan-image.seed\"\n }\n },\n \"response_format\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.providerOptions.volcengine-ark-agent-plan-image.response_format\"\n },\n \"b64_json\"\n ]\n }\n }\n },\n \"response\": {\n \"status\": \"succeeded\",\n \"images\": {\n \"$ref\": \"response.data\"\n },\n \"usage\": {\n \"$ref\": \"response.usage\"\n },\n \"errorPaths\": [\n \"error.code\"\n ],\n \"messagePaths\": [\n \"error.message\"\n ]\n }\n }\n ]\n }\n}\n```\n\n", + "permissions": [ + "generation.run", + "media.read" + ], + "configuration": { + "fields": [ + { + "name": "apiKey", + "type": "secret", + "label": "API Key", + "required": true + } + ] + }, + "contributes": { + "providers": [ + { + "id": "volcengine-ark-agent-plan-image", + "label": "Volcengine Ark Agent Plan Seedream Images", + "capabilities": [ + "image" + ], + "scopes": [ + "admin.system-channel", + "user.custom-channel", + "canvas", + "creation", + "agent" + ], + "baseUrl": "https://ark.cn-beijing.volces.com", + "requiresPublicMediaUrls": false, + "auth": { + "type": "bearer", + "field": "apiKey" + }, + "parameters": [ + { + "name": "model", + "type": "string", + "required": true, + "mapping": "model", + "description": "图片模型 ID。" + }, + { + "name": "prompt", + "type": "string", + "required": true, + "mapping": "prompt", + "description": "图片提示词。" + }, + { + "name": "images", + "type": "media[]", + "required": false, + "mapping": "provider image/reference fields", + "description": "参考图或编辑源图,role 由业务层确定。" + }, + { + "name": "imageCount", + "type": "integer", + "required": false, + "mapping": "n/sample_count", + "description": "输出数量。" + }, + { + "name": "aspectRatio", + "type": "string", + "required": false, + "mapping": "size/aspect_ratio", + "description": "比例或尺寸,语义按协议说明。" + }, + { + "name": "resolution", + "type": "string", + "required": false, + "mapping": "resolution/imageSize", + "description": "分辨率档位。" + }, + { + "name": "quality", + "type": "string", + "required": false, + "mapping": "quality", + "description": "质量档位。" + }, + { + "name": "providerOptions", + "type": "object", + "required": false, + "mapping": "provider-specific fields", + "description": "插件命名空间内的厂商扩展字段。" + } + ], + "create": { + "method": "POST", + "path": "/api/plan/v3/images/generations", + "contentType": "application/json", + "body": { + "model": { + "$ref": "request.model" + }, + "prompt": { + "$ref": "request.prompt" + }, + "size": { + "$omitEmpty": { + "$switch": { + "cases": [ + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "auto" + ] + }, + "then": "2k" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1:1" + ] + }, + "then": "2048x2048" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "4:3" + ] + }, + "then": "2304x1728" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "3:4" + ] + }, + "then": "1728x2304" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "16:9" + ] + }, + "then": "2560x1440" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "9:16" + ] + }, + "then": "1440x2560" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "3:2" + ] + }, + "then": "2496x1664" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "2:3" + ] + }, + "then": "1664x2496" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "21:9" + ] + }, + "then": "3024x1296" + } + ], + "default": { + "$ref": "request.aspectRatio" + } + } + } + }, + "image": { + "$omitEmpty": { + "$if": { + "condition": { + "$eq": [ + { + "$len": { + "$ref": "request.images" + } + }, + 1 + ] + }, + "then": { + "$first": { + "$map": { + "from": { + "$ref": "request.images" + }, + "as": "media", + "in": { + "$ref": "media.value" + } + } + } + }, + "else": { + "$if": { + "condition": { + "$gt": [ + { + "$len": { + "$ref": "request.images" + } + }, + 1 + ] + }, + "then": { + "$map": { + "from": { + "$ref": "request.images" + }, + "as": "media", + "in": { + "$ref": "media.value" + } + } + }, + "else": null + } + } + } + } + }, + "sequential_image_generation": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation" + } + }, + "sequential_image_generation_options": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.sequential_image_generation_options" + } + }, + "watermark": { + "$coalesce": [ + { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.watermark" + }, + { + "$ref": "request.watermark" + }, + false + ] + }, + "seed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.seed" + } + }, + "response_format": { + "$coalesce": [ + { + "$ref": "request.providerOptions.volcengine-ark-agent-plan-image.response_format" + }, + "b64_json" + ] + } + } + }, + "response": { + "status": "succeeded", + "images": { + "$ref": "response.data" + }, + "usage": { + "$ref": "response.usage" + }, + "errorPaths": [ + "error.code" + ], + "messagePaths": [ + "error.message" + ] + } + } + ] + } +} diff --git a/plugin-packages/volcengine-ark-seedance.yingce-plugin b/plugin-packages/volcengine-ark-seedance.yingce-plugin index 9d0700c14..f95dc8a1d 100644 Binary files a/plugin-packages/volcengine-ark-seedance.yingce-plugin and b/plugin-packages/volcengine-ark-seedance.yingce-plugin differ diff --git a/plugin-packages/volcengine-ark-seedance/README.md b/plugin-packages/volcengine-ark-seedance/README.md new file mode 100644 index 000000000..859054a7d --- /dev/null +++ b/plugin-packages/volcengine-ark-seedance/README.md @@ -0,0 +1,5 @@ +# Volcengine Ark Seedance + +该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-seedance.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。 + +完整接口见 [docs/interface.md](docs/interface.md)。 diff --git a/plugin-packages/volcengine-ark-seedance/docs/interface.md b/plugin-packages/volcengine-ark-seedance/docs/interface.md new file mode 100644 index 000000000..c71a76aba --- /dev/null +++ b/plugin-packages/volcengine-ark-seedance/docs/interface.md @@ -0,0 +1,527 @@ +# Volcengine Ark Seedance 接口字段 + +## 协议身份 + +- 插件 ID:`volcengine-ark-seedance`。 +- Provider ID:`volcengine-ark-video`。 +- 能力:`video`。 +- 默认 Base URL:`https://ark.cn-beijing.volces.com`。 +- 鉴权驱动:`bearer`。 +- 创建:`POST /api/v3/contents/generations/tasks`。 +- 查询:`GET /api/v3/contents/generations/tasks/{{taskId}}`。 +- 取消:`DELETE /api/v3/contents/generations/tasks/{{taskId}}`。 + +## 配置字段 + +| 字段 | 类型 | 必填 | 含义 | +| --- | --- | --- | --- | +| `apiKey` | secret | 是 | API Key | + +## 统一字段映射 + +| 统一字段 | 类型 | 必填 | 上游映射 | 说明 | +| --- | --- | --- | --- | --- | +| `model` | string | 是 | `model` | Ark endpoint/model ID。 | +| `prompt` | string | 是 | `content[type=text].text` | 视频提示词。 | +| `images` | media[] | 否 | `content[type=image_url]` | first_frame、last_frame、reference_image 等 role 原样映射。 | +| `videos` | media[] | 否 | `content[type=video_url]` | reference_video。 | +| `audios` | media[] | 否 | `content[type=audio_url]` | reference_audio/reference_voice。 | +| `aspectRatio` | string | 否 | `ratio` | 输出画幅。 | +| `resolution` | string | 否 | `resolution` | 输出分辨率档位。 | +| `duration` | integer | 否 | `duration` | 输出时长秒数。 | +| `generateAudio` | boolean | 否 | `generate_audio` | 是否生成音频。 | +| `watermark` | boolean | 否 | `watermark` | 是否带水印。 | +| `seed` | integer | 否 | `seed` | providerOptions seed。 | +| `camera_fixed` | boolean | 否 | `camera_fixed` | providerOptions camera_fixed。 | + +## 上游请求模板逐字段清单 + +下表由插件请求模板生成,覆盖 body、query、headers 和 multipart 文件声明中的每个字段。 + +| 上游位置 | 值或转换表达式 | +| --- | --- | +| `create.method` | `"POST"` | +| `create.path` | `"/api/v3/contents/generations/tasks"` | +| `create.contentType` | `"application/json"` | +| `create.body.model` | `{"$ref":"request.model"}` | +| `create.body.content` | `{"$concatArrays":[[{"type":"text","text":{"$ref":"request.prompt"}}],{"$map":{"from":{"$sortByOrder":{"$ref":"request.images"}},"as":"media","in":{"type":"image_url","image_url":{"url":{"$ref":"media.value"}},"role":{"$coalesce":[{"$ref":"media.role"},"reference_image"]}}}},{"$map":{"from":{"$sortByOrder":{"$ref":"request.videos"}},"as":"media","in":{"type":"video_url","video_url":{"url":{"$ref":"media.value"}},"role":{"$coalesce":[{"$ref":"media.role"},"reference_video"]}}}},{"$map":{"from":{"$sortByOrder":{"$ref":"request.audios"}},"as":"media","in":{"type":"audio_url","audio_url":{"url":{"$ref":"media.value"}},"role":{"$coalesce":[{"$ref":"media.role"},"reference_audio"]}}}}]}` | +| `create.body.ratio` | `{"$coalesce":[{"$ref":"request.aspectRatio"},"16:9"]}` | +| `create.body.resolution` | `{"$coalesce":[{"$ref":"request.resolution"},"720p"]}` | +| `create.body.duration` | `{"$if":{"condition":{"$gt":[{"$ref":"request.duration"},0]},"then":{"$ref":"request.duration"},"else":5}}` | +| `create.body.generate_audio` | `{"$ref":"request.generateAudio"}` | +| `create.body.watermark` | `{"$ref":"request.watermark"}` | +| `create.body.seed` | `{"$omitEmpty":{"$ref":"request.providerOptions.volcengine-ark-video.seed"}}` | +| `create.body.camera_fixed` | `{"$omitEmpty":{"$ref":"request.providerOptions.volcengine-ark-video.camera_fixed"}}` | +| `poll.method` | `"GET"` | +| `poll.path` | `"/api/v3/contents/generations/tasks/{{taskId}}"` | +| `poll.contentType` | `"application/json"` | +| `cancel.method` | `"DELETE"` | +| `cancel.path` | `"/api/v3/contents/generations/tasks/{{taskId}}"` | +| `cancel.contentType` | `"application/json"` | + +## Provider 扩展键 + +- `providerOptions.volcengine-ark-video.camera_fixed` +- `providerOptions.volcengine-ark-video.seed` + +动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。 + +## 响应映射逐字段清单 + +| 映射位置 | 上游路径或转换表达式 | +| --- | --- | +| `response.taskId` | `{"$coalesce":[{"$ref":"response.id"},{"$ref":"response.task_id"},{"$ref":"response.data.id"},{"$ref":"taskId"}]}` | +| `response.status` | `{"$coalesce":[{"$ref":"response.status"},{"$ref":"response.data.status"},"pending"]}` | +| `response.message` | `{"$coalesce":[{"$ref":"response.error.message"},{"$ref":"response.message"},{"$ref":"response.fail_reason"}]}` | +| `response.videos` | `{"$coalesce":[{"$ref":"response.content.video_url"},{"$ref":"response.video_url"},{"$ref":"response.output.video_url"},{"$ref":"response.data.video_url"}]}` | +| `response.usage` | `{"$ref":"response.usage"}` | +| `response.errorPaths[0]` | `"error.code"` | +| `response.resultEphemeral` | `true` | + +## 响应与错误 + +插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。 + +## 兼容边界 + +官方 Ark 推理接入:创建/查询/取消走 /api/v3/contents/generations/tasks;插件不根据图片下标推断首尾帧,role 由业务层确定。API Key 来自方舟推理接入控制台。 + + +## Manifest 完整接口定义 + +以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。 + +```json +{ + "apiVersion": "yingce.plugin/v2", + "id": "volcengine-ark-seedance", + "name": "Volcengine Ark Seedance", + "version": "2.0.0", + "author": "Volcengine / 影策", + "description": "Volcengine Ark Seedance 独立请求协议插件。", + "documentation": "<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>", + "permissions": [ + "generation.run", + "media.read" + ], + "configuration": { + "fields": [ + { + "name": "apiKey", + "type": "secret", + "label": "API Key", + "required": true + } + ] + }, + "contributes": { + "providers": [ + { + "id": "volcengine-ark-video", + "label": "Volcengine Ark Seedance", + "capabilities": [ + "video" + ], + "scopes": [ + "admin.system-channel", + "user.custom-channel", + "canvas", + "creation", + "agent" + ], + "baseUrl": "https://ark.cn-beijing.volces.com", + "requiresPublicMediaUrls": true, + "auth": { + "type": "bearer", + "field": "apiKey" + }, + "parameters": [ + { + "name": "model", + "type": "string", + "required": true, + "mapping": "model", + "description": "Ark endpoint/model ID。" + }, + { + "name": "prompt", + "type": "string", + "required": true, + "mapping": "content[type=text].text", + "description": "视频提示词。" + }, + { + "name": "images", + "type": "media[]", + "required": false, + "mapping": "content[type=image_url]", + "description": "first_frame、last_frame、reference_image 等 role 原样映射。" + }, + { + "name": "videos", + "type": "media[]", + "required": false, + "mapping": "content[type=video_url]", + "description": "reference_video。" + }, + { + "name": "audios", + "type": "media[]", + "required": false, + "mapping": "content[type=audio_url]", + "description": "reference_audio/reference_voice。" + }, + { + "name": "aspectRatio", + "type": "string", + "required": false, + "mapping": "ratio", + "description": "输出画幅。" + }, + { + "name": "resolution", + "type": "string", + "required": false, + "mapping": "resolution", + "description": "输出分辨率档位。" + }, + { + "name": "duration", + "type": "integer", + "required": false, + "mapping": "duration", + "description": "输出时长秒数。" + }, + { + "name": "generateAudio", + "type": "boolean", + "required": false, + "mapping": "generate_audio", + "description": "是否生成音频。" + }, + { + "name": "watermark", + "type": "boolean", + "required": false, + "mapping": "watermark", + "description": "是否带水印。" + }, + { + "name": "seed", + "type": "integer", + "required": false, + "mapping": "seed", + "description": "providerOptions seed。" + }, + { + "name": "camera_fixed", + "type": "boolean", + "required": false, + "mapping": "camera_fixed", + "description": "providerOptions camera_fixed。" + } + ], + "validations": [ + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.images" + } + }, + 9 + ] + }, + "message": "Seedance 最多支持 9 张参考图片" + }, + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.videos" + } + }, + 3 + ] + }, + "message": "Seedance 最多支持 3 个参考视频" + }, + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.audios" + } + }, + 3 + ] + }, + "message": "Seedance 最多支持 3 个参考音频" + }, + { + "assert": { + "$or": [ + { + "$eq": [ + { + "$len": { + "$ref": "request.audios" + } + }, + 0 + ] + }, + { + "$gt": [ + { + "$add": [ + { + "$len": { + "$ref": "request.images" + } + }, + { + "$len": { + "$ref": "request.videos" + } + } + ] + }, + 0 + ] + } + ] + }, + "message": "Seedance 不支持纯音频或文本+音频,请同时添加参考图片或参考视频" + } + ], + "create": { + "method": "POST", + "path": "/api/v3/contents/generations/tasks", + "contentType": "application/json", + "body": { + "model": { + "$ref": "request.model" + }, + "content": { + "$concatArrays": [ + [ + { + "type": "text", + "text": { + "$ref": "request.prompt" + } + } + ], + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.images" + } + }, + "as": "media", + "in": { + "type": "image_url", + "image_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_image" + ] + } + } + } + }, + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.videos" + } + }, + "as": "media", + "in": { + "type": "video_url", + "video_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_video" + ] + } + } + } + }, + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.audios" + } + }, + "as": "media", + "in": { + "type": "audio_url", + "audio_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_audio" + ] + } + } + } + } + ] + }, + "ratio": { + "$coalesce": [ + { + "$ref": "request.aspectRatio" + }, + "16:9" + ] + }, + "resolution": { + "$coalesce": [ + { + "$ref": "request.resolution" + }, + "720p" + ] + }, + "duration": { + "$if": { + "condition": { + "$gt": [ + { + "$ref": "request.duration" + }, + 0 + ] + }, + "then": { + "$ref": "request.duration" + }, + "else": 5 + } + }, + "generate_audio": { + "$ref": "request.generateAudio" + }, + "watermark": { + "$ref": "request.watermark" + }, + "seed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-video.seed" + } + }, + "camera_fixed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-video.camera_fixed" + } + } + } + }, + "poll": { + "method": "GET", + "path": "/api/v3/contents/generations/tasks/{{taskId}}" + }, + "cancel": { + "method": "DELETE", + "path": "/api/v3/contents/generations/tasks/{{taskId}}" + }, + "response": { + "taskId": { + "$coalesce": [ + { + "$ref": "response.id" + }, + { + "$ref": "response.task_id" + }, + { + "$ref": "response.data.id" + }, + { + "$ref": "taskId" + } + ] + }, + "status": { + "$coalesce": [ + { + "$ref": "response.status" + }, + { + "$ref": "response.data.status" + }, + "pending" + ] + }, + "message": { + "$coalesce": [ + { + "$ref": "response.error.message" + }, + { + "$ref": "response.message" + }, + { + "$ref": "response.fail_reason" + } + ] + }, + "videos": { + "$coalesce": [ + { + "$ref": "response.content.video_url" + }, + { + "$ref": "response.video_url" + }, + { + "$ref": "response.output.video_url" + }, + { + "$ref": "response.data.video_url" + } + ] + }, + "usage": { + "$ref": "response.usage" + }, + "errorPaths": [ + "error.code" + ], + "resultEphemeral": true + } + } + ] + } +} +``` + diff --git a/plugin-packages/volcengine-ark-seedance/manifest.json b/plugin-packages/volcengine-ark-seedance/manifest.json new file mode 100644 index 000000000..facb8c063 --- /dev/null +++ b/plugin-packages/volcengine-ark-seedance/manifest.json @@ -0,0 +1,431 @@ +{ + "apiVersion": "yingce.plugin/v2", + "id": "volcengine-ark-seedance", + "name": "Volcengine Ark Seedance", + "version": "2.0.0", + "author": "Volcengine / 影策", + "description": "Volcengine Ark Seedance 独立请求协议插件。", + "documentation": "# Volcengine Ark Seedance\n\n该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-seedance.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。\n\n完整接口见 [docs/interface.md](docs/interface.md)。\n\n---\n\n# Volcengine Ark Seedance 接口字段\n\n## 协议身份\n\n- 插件 ID:`volcengine-ark-seedance`。\n- Provider ID:`volcengine-ark-video`。\n- 能力:`video`。\n- 默认 Base URL:`https://ark.cn-beijing.volces.com`。\n- 鉴权驱动:`bearer`。\n- 创建:`POST /api/v3/contents/generations/tasks`。\n- 查询:`GET /api/v3/contents/generations/tasks/{{taskId}}`。\n- 取消:`DELETE /api/v3/contents/generations/tasks/{{taskId}}`。\n\n## 配置字段\n\n| 字段 | 类型 | 必填 | 含义 |\n| --- | --- | --- | --- |\n| `apiKey` | secret | 是 | API Key |\n\n## 统一字段映射\n\n| 统一字段 | 类型 | 必填 | 上游映射 | 说明 |\n| --- | --- | --- | --- | --- |\n| `model` | string | 是 | `model` | Ark endpoint/model ID。 |\n| `prompt` | string | 是 | `content[type=text].text` | 视频提示词。 |\n| `images` | media[] | 否 | `content[type=image_url]` | first_frame、last_frame、reference_image 等 role 原样映射。 |\n| `videos` | media[] | 否 | `content[type=video_url]` | reference_video。 |\n| `audios` | media[] | 否 | `content[type=audio_url]` | reference_audio/reference_voice。 |\n| `aspectRatio` | string | 否 | `ratio` | 输出画幅。 |\n| `resolution` | string | 否 | `resolution` | 输出分辨率档位。 |\n| `duration` | integer | 否 | `duration` | 输出时长秒数。 |\n| `generateAudio` | boolean | 否 | `generate_audio` | 是否生成音频。 |\n| `watermark` | boolean | 否 | `watermark` | 是否带水印。 |\n| `seed` | integer | 否 | `seed` | providerOptions seed。 |\n| `camera_fixed` | boolean | 否 | `camera_fixed` | providerOptions camera_fixed。 |\n\n## 上游请求模板逐字段清单\n\n下表由插件请求模板生成,覆盖 body、query、headers 和 multipart 文件声明中的每个字段。\n\n| 上游位置 | 值或转换表达式 |\n| --- | --- |\n| `create.method` | `\"POST\"` |\n| `create.path` | `\"/api/v3/contents/generations/tasks\"` |\n| `create.contentType` | `\"application/json\"` |\n| `create.body.model` | `{\"$ref\":\"request.model\"}` |\n| `create.body.content` | `{\"$concatArrays\":[[{\"type\":\"text\",\"text\":{\"$ref\":\"request.prompt\"}}],{\"$map\":{\"from\":{\"$sortByOrder\":{\"$ref\":\"request.images\"}},\"as\":\"media\",\"in\":{\"type\":\"image_url\",\"image_url\":{\"url\":{\"$ref\":\"media.value\"}},\"role\":{\"$coalesce\":[{\"$ref\":\"media.role\"},\"reference_image\"]}}}},{\"$map\":{\"from\":{\"$sortByOrder\":{\"$ref\":\"request.videos\"}},\"as\":\"media\",\"in\":{\"type\":\"video_url\",\"video_url\":{\"url\":{\"$ref\":\"media.value\"}},\"role\":{\"$coalesce\":[{\"$ref\":\"media.role\"},\"reference_video\"]}}}},{\"$map\":{\"from\":{\"$sortByOrder\":{\"$ref\":\"request.audios\"}},\"as\":\"media\",\"in\":{\"type\":\"audio_url\",\"audio_url\":{\"url\":{\"$ref\":\"media.value\"}},\"role\":{\"$coalesce\":[{\"$ref\":\"media.role\"},\"reference_audio\"]}}}}]}` |\n| `create.body.ratio` | `{\"$coalesce\":[{\"$ref\":\"request.aspectRatio\"},\"16:9\"]}` |\n| `create.body.resolution` | `{\"$coalesce\":[{\"$ref\":\"request.resolution\"},\"720p\"]}` |\n| `create.body.duration` | `{\"$if\":{\"condition\":{\"$gt\":[{\"$ref\":\"request.duration\"},0]},\"then\":{\"$ref\":\"request.duration\"},\"else\":5}}` |\n| `create.body.generate_audio` | `{\"$ref\":\"request.generateAudio\"}` |\n| `create.body.watermark` | `{\"$ref\":\"request.watermark\"}` |\n| `create.body.seed` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-video.seed\"}}` |\n| `create.body.camera_fixed` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-video.camera_fixed\"}}` |\n| `poll.method` | `\"GET\"` |\n| `poll.path` | `\"/api/v3/contents/generations/tasks/{{taskId}}\"` |\n| `poll.contentType` | `\"application/json\"` |\n| `cancel.method` | `\"DELETE\"` |\n| `cancel.path` | `\"/api/v3/contents/generations/tasks/{{taskId}}\"` |\n| `cancel.contentType` | `\"application/json\"` |\n\n## Provider 扩展键\n\n- `providerOptions.volcengine-ark-video.camera_fixed`\n- `providerOptions.volcengine-ark-video.seed`\n\n动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。\n\n## 响应映射逐字段清单\n\n| 映射位置 | 上游路径或转换表达式 |\n| --- | --- |\n| `response.taskId` | `{\"$coalesce\":[{\"$ref\":\"response.id\"},{\"$ref\":\"response.task_id\"},{\"$ref\":\"response.data.id\"},{\"$ref\":\"taskId\"}]}` |\n| `response.status` | `{\"$coalesce\":[{\"$ref\":\"response.status\"},{\"$ref\":\"response.data.status\"},\"pending\"]}` |\n| `response.message` | `{\"$coalesce\":[{\"$ref\":\"response.error.message\"},{\"$ref\":\"response.message\"},{\"$ref\":\"response.fail_reason\"}]}` |\n| `response.videos` | `{\"$coalesce\":[{\"$ref\":\"response.content.video_url\"},{\"$ref\":\"response.video_url\"},{\"$ref\":\"response.output.video_url\"},{\"$ref\":\"response.data.video_url\"}]}` |\n| `response.usage` | `{\"$ref\":\"response.usage\"}` |\n| `response.errorPaths[0]` | `\"error.code\"` |\n| `response.resultEphemeral` | `true` |\n\n## 响应与错误\n\n插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。\n\n## 兼容边界\n\n官方 Ark 推理接入:创建/查询/取消走 /api/v3/contents/generations/tasks;插件不根据图片下标推断首尾帧,role 由业务层确定。API Key 来自方舟推理接入控制台。\n\n\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v2\",\n \"id\": \"volcengine-ark-seedance\",\n \"name\": \"Volcengine Ark Seedance\",\n \"version\": \"2.0.0\",\n \"author\": \"Volcengine / 影策\",\n \"description\": \"Volcengine Ark Seedance 独立请求协议插件。\",\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\",\n \"permissions\": [\n \"generation.run\",\n \"media.read\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"apiKey\",\n \"type\": \"secret\",\n \"label\": \"API Key\",\n \"required\": true\n }\n ]\n },\n \"contributes\": {\n \"providers\": [\n {\n \"id\": \"volcengine-ark-video\",\n \"label\": \"Volcengine Ark Seedance\",\n \"capabilities\": [\n \"video\"\n ],\n \"scopes\": [\n \"admin.system-channel\",\n \"user.custom-channel\",\n \"canvas\",\n \"creation\",\n \"agent\"\n ],\n \"baseUrl\": \"https://ark.cn-beijing.volces.com\",\n \"requiresPublicMediaUrls\": true,\n \"auth\": {\n \"type\": \"bearer\",\n \"field\": \"apiKey\"\n },\n \"parameters\": [\n {\n \"name\": \"model\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"model\",\n \"description\": \"Ark endpoint/model ID。\"\n },\n {\n \"name\": \"prompt\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"content[type=text].text\",\n \"description\": \"视频提示词。\"\n },\n {\n \"name\": \"images\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"content[type=image_url]\",\n \"description\": \"first_frame、last_frame、reference_image 等 role 原样映射。\"\n },\n {\n \"name\": \"videos\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"content[type=video_url]\",\n \"description\": \"reference_video。\"\n },\n {\n \"name\": \"audios\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"content[type=audio_url]\",\n \"description\": \"reference_audio/reference_voice。\"\n },\n {\n \"name\": \"aspectRatio\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"ratio\",\n \"description\": \"输出画幅。\"\n },\n {\n \"name\": \"resolution\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"resolution\",\n \"description\": \"输出分辨率档位。\"\n },\n {\n \"name\": \"duration\",\n \"type\": \"integer\",\n \"required\": false,\n \"mapping\": \"duration\",\n \"description\": \"输出时长秒数。\"\n },\n {\n \"name\": \"generateAudio\",\n \"type\": \"boolean\",\n \"required\": false,\n \"mapping\": \"generate_audio\",\n \"description\": \"是否生成音频。\"\n },\n {\n \"name\": \"watermark\",\n \"type\": \"boolean\",\n \"required\": false,\n \"mapping\": \"watermark\",\n \"description\": \"是否带水印。\"\n },\n {\n \"name\": \"seed\",\n \"type\": \"integer\",\n \"required\": false,\n \"mapping\": \"seed\",\n \"description\": \"providerOptions seed。\"\n },\n {\n \"name\": \"camera_fixed\",\n \"type\": \"boolean\",\n \"required\": false,\n \"mapping\": \"camera_fixed\",\n \"description\": \"providerOptions camera_fixed。\"\n }\n ],\n \"validations\": [\n {\n \"assert\": {\n \"$lte\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n 9\n ]\n },\n \"message\": \"Seedance 最多支持 9 张参考图片\"\n },\n {\n \"assert\": {\n \"$lte\": [\n {\n \"$len\": {\n \"$ref\": \"request.videos\"\n }\n },\n 3\n ]\n },\n \"message\": \"Seedance 最多支持 3 个参考视频\"\n },\n {\n \"assert\": {\n \"$lte\": [\n {\n \"$len\": {\n \"$ref\": \"request.audios\"\n }\n },\n 3\n ]\n },\n \"message\": \"Seedance 最多支持 3 个参考音频\"\n },\n {\n \"assert\": {\n \"$or\": [\n {\n \"$eq\": [\n {\n \"$len\": {\n \"$ref\": \"request.audios\"\n }\n },\n 0\n ]\n },\n {\n \"$gt\": [\n {\n \"$add\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n {\n \"$len\": {\n \"$ref\": \"request.videos\"\n }\n }\n ]\n },\n 0\n ]\n }\n ]\n },\n \"message\": \"Seedance 不支持纯音频或文本+音频,请同时添加参考图片或参考视频\"\n }\n ],\n \"create\": {\n \"method\": \"POST\",\n \"path\": \"/api/v3/contents/generations/tasks\",\n \"contentType\": \"application/json\",\n \"body\": {\n \"model\": {\n \"$ref\": \"request.model\"\n },\n \"content\": {\n \"$concatArrays\": [\n [\n {\n \"type\": \"text\",\n \"text\": {\n \"$ref\": \"request.prompt\"\n }\n }\n ],\n {\n \"$map\": {\n \"from\": {\n \"$sortByOrder\": {\n \"$ref\": \"request.images\"\n }\n },\n \"as\": \"media\",\n \"in\": {\n \"type\": \"image_url\",\n \"image_url\": {\n \"url\": {\n \"$ref\": \"media.value\"\n }\n },\n \"role\": {\n \"$coalesce\": [\n {\n \"$ref\": \"media.role\"\n },\n \"reference_image\"\n ]\n }\n }\n }\n },\n {\n \"$map\": {\n \"from\": {\n \"$sortByOrder\": {\n \"$ref\": \"request.videos\"\n }\n },\n \"as\": \"media\",\n \"in\": {\n \"type\": \"video_url\",\n \"video_url\": {\n \"url\": {\n \"$ref\": \"media.value\"\n }\n },\n \"role\": {\n \"$coalesce\": [\n {\n \"$ref\": \"media.role\"\n },\n \"reference_video\"\n ]\n }\n }\n }\n },\n {\n \"$map\": {\n \"from\": {\n \"$sortByOrder\": {\n \"$ref\": \"request.audios\"\n }\n },\n \"as\": \"media\",\n \"in\": {\n \"type\": \"audio_url\",\n \"audio_url\": {\n \"url\": {\n \"$ref\": \"media.value\"\n }\n },\n \"role\": {\n \"$coalesce\": [\n {\n \"$ref\": \"media.role\"\n },\n \"reference_audio\"\n ]\n }\n }\n }\n }\n ]\n },\n \"ratio\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"16:9\"\n ]\n },\n \"resolution\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.resolution\"\n },\n \"720p\"\n ]\n },\n \"duration\": {\n \"$if\": {\n \"condition\": {\n \"$gt\": [\n {\n \"$ref\": \"request.duration\"\n },\n 0\n ]\n },\n \"then\": {\n \"$ref\": \"request.duration\"\n },\n \"else\": 5\n }\n },\n \"generate_audio\": {\n \"$ref\": \"request.generateAudio\"\n },\n \"watermark\": {\n \"$ref\": \"request.watermark\"\n },\n \"seed\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-video.seed\"\n }\n },\n \"camera_fixed\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-video.camera_fixed\"\n }\n }\n }\n },\n \"poll\": {\n \"method\": \"GET\",\n \"path\": \"/api/v3/contents/generations/tasks/{{taskId}}\"\n },\n \"cancel\": {\n \"method\": \"DELETE\",\n \"path\": \"/api/v3/contents/generations/tasks/{{taskId}}\"\n },\n \"response\": {\n \"taskId\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.id\"\n },\n {\n \"$ref\": \"response.task_id\"\n },\n {\n \"$ref\": \"response.data.id\"\n },\n {\n \"$ref\": \"taskId\"\n }\n ]\n },\n \"status\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.status\"\n },\n {\n \"$ref\": \"response.data.status\"\n },\n \"pending\"\n ]\n },\n \"message\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.error.message\"\n },\n {\n \"$ref\": \"response.message\"\n },\n {\n \"$ref\": \"response.fail_reason\"\n }\n ]\n },\n \"videos\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.content.video_url\"\n },\n {\n \"$ref\": \"response.video_url\"\n },\n {\n \"$ref\": \"response.output.video_url\"\n },\n {\n \"$ref\": \"response.data.video_url\"\n }\n ]\n },\n \"usage\": {\n \"$ref\": \"response.usage\"\n },\n \"errorPaths\": [\n \"error.code\"\n ],\n \"resultEphemeral\": true\n }\n }\n ]\n }\n}\n```\n\n", + "permissions": [ + "generation.run", + "media.read" + ], + "configuration": { + "fields": [ + { + "name": "apiKey", + "type": "secret", + "label": "API Key", + "required": true + } + ] + }, + "contributes": { + "providers": [ + { + "id": "volcengine-ark-video", + "label": "Volcengine Ark Seedance", + "capabilities": [ + "video" + ], + "scopes": [ + "admin.system-channel", + "user.custom-channel", + "canvas", + "creation", + "agent" + ], + "baseUrl": "https://ark.cn-beijing.volces.com", + "requiresPublicMediaUrls": true, + "auth": { + "type": "bearer", + "field": "apiKey" + }, + "parameters": [ + { + "name": "model", + "type": "string", + "required": true, + "mapping": "model", + "description": "Ark endpoint/model ID。" + }, + { + "name": "prompt", + "type": "string", + "required": true, + "mapping": "content[type=text].text", + "description": "视频提示词。" + }, + { + "name": "images", + "type": "media[]", + "required": false, + "mapping": "content[type=image_url]", + "description": "first_frame、last_frame、reference_image 等 role 原样映射。" + }, + { + "name": "videos", + "type": "media[]", + "required": false, + "mapping": "content[type=video_url]", + "description": "reference_video。" + }, + { + "name": "audios", + "type": "media[]", + "required": false, + "mapping": "content[type=audio_url]", + "description": "reference_audio/reference_voice。" + }, + { + "name": "aspectRatio", + "type": "string", + "required": false, + "mapping": "ratio", + "description": "输出画幅。" + }, + { + "name": "resolution", + "type": "string", + "required": false, + "mapping": "resolution", + "description": "输出分辨率档位。" + }, + { + "name": "duration", + "type": "integer", + "required": false, + "mapping": "duration", + "description": "输出时长秒数。" + }, + { + "name": "generateAudio", + "type": "boolean", + "required": false, + "mapping": "generate_audio", + "description": "是否生成音频。" + }, + { + "name": "watermark", + "type": "boolean", + "required": false, + "mapping": "watermark", + "description": "是否带水印。" + }, + { + "name": "seed", + "type": "integer", + "required": false, + "mapping": "seed", + "description": "providerOptions seed。" + }, + { + "name": "camera_fixed", + "type": "boolean", + "required": false, + "mapping": "camera_fixed", + "description": "providerOptions camera_fixed。" + } + ], + "validations": [ + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.images" + } + }, + 9 + ] + }, + "message": "Seedance 最多支持 9 张参考图片" + }, + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.videos" + } + }, + 3 + ] + }, + "message": "Seedance 最多支持 3 个参考视频" + }, + { + "assert": { + "$lte": [ + { + "$len": { + "$ref": "request.audios" + } + }, + 3 + ] + }, + "message": "Seedance 最多支持 3 个参考音频" + }, + { + "assert": { + "$or": [ + { + "$eq": [ + { + "$len": { + "$ref": "request.audios" + } + }, + 0 + ] + }, + { + "$gt": [ + { + "$add": [ + { + "$len": { + "$ref": "request.images" + } + }, + { + "$len": { + "$ref": "request.videos" + } + } + ] + }, + 0 + ] + } + ] + }, + "message": "Seedance 不支持纯音频或文本+音频,请同时添加参考图片或参考视频" + } + ], + "create": { + "method": "POST", + "path": "/api/v3/contents/generations/tasks", + "contentType": "application/json", + "body": { + "model": { + "$ref": "request.model" + }, + "content": { + "$concatArrays": [ + [ + { + "type": "text", + "text": { + "$ref": "request.prompt" + } + } + ], + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.images" + } + }, + "as": "media", + "in": { + "type": "image_url", + "image_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_image" + ] + } + } + } + }, + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.videos" + } + }, + "as": "media", + "in": { + "type": "video_url", + "video_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_video" + ] + } + } + } + }, + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.audios" + } + }, + "as": "media", + "in": { + "type": "audio_url", + "audio_url": { + "url": { + "$ref": "media.value" + } + }, + "role": { + "$coalesce": [ + { + "$ref": "media.role" + }, + "reference_audio" + ] + } + } + } + } + ] + }, + "ratio": { + "$coalesce": [ + { + "$ref": "request.aspectRatio" + }, + "16:9" + ] + }, + "resolution": { + "$coalesce": [ + { + "$ref": "request.resolution" + }, + "720p" + ] + }, + "duration": { + "$if": { + "condition": { + "$gt": [ + { + "$ref": "request.duration" + }, + 0 + ] + }, + "then": { + "$ref": "request.duration" + }, + "else": 5 + } + }, + "generate_audio": { + "$ref": "request.generateAudio" + }, + "watermark": { + "$ref": "request.watermark" + }, + "seed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-video.seed" + } + }, + "camera_fixed": { + "$omitEmpty": { + "$ref": "request.providerOptions.volcengine-ark-video.camera_fixed" + } + } + } + }, + "poll": { + "method": "GET", + "path": "/api/v3/contents/generations/tasks/{{taskId}}" + }, + "cancel": { + "method": "DELETE", + "path": "/api/v3/contents/generations/tasks/{{taskId}}" + }, + "response": { + "taskId": { + "$coalesce": [ + { + "$ref": "response.id" + }, + { + "$ref": "response.task_id" + }, + { + "$ref": "response.data.id" + }, + { + "$ref": "taskId" + } + ] + }, + "status": { + "$coalesce": [ + { + "$ref": "response.status" + }, + { + "$ref": "response.data.status" + }, + "pending" + ] + }, + "message": { + "$coalesce": [ + { + "$ref": "response.error.message" + }, + { + "$ref": "response.message" + }, + { + "$ref": "response.fail_reason" + } + ] + }, + "videos": { + "$coalesce": [ + { + "$ref": "response.content.video_url" + }, + { + "$ref": "response.video_url" + }, + { + "$ref": "response.output.video_url" + }, + { + "$ref": "response.data.video_url" + } + ] + }, + "usage": { + "$ref": "response.usage" + }, + "errorPaths": [ + "error.code" + ], + "resultEphemeral": true + } + } + ] + } +} diff --git a/plugin-packages/volcengine-ark-seedream.yingce-plugin b/plugin-packages/volcengine-ark-seedream.yingce-plugin index eb223b159..0eeb378b6 100644 Binary files a/plugin-packages/volcengine-ark-seedream.yingce-plugin and b/plugin-packages/volcengine-ark-seedream.yingce-plugin differ diff --git a/plugin-packages/volcengine-ark-seedream/docs/interface.md b/plugin-packages/volcengine-ark-seedream/docs/interface.md index 8e6883d5b..c3bb2a96b 100644 --- a/plugin-packages/volcengine-ark-seedream/docs/interface.md +++ b/plugin-packages/volcengine-ark-seedream/docs/interface.md @@ -74,7 +74,7 @@ ## 兼容边界 -该包只代表上述线协议 profile;同一品牌的其他 endpoint、云区域或网关包装必须使用独立插件,不能根据模型名猜测。 +官方 Ark 推理接入:Base URL 使用 /api/v3,API Key 来自方舟推理接入控制台,不可与 Agent Plan 专属 Key 混用。 ## Manifest 完整接口定义 diff --git a/plugin-packages/volcengine-ark-seedream/manifest.json b/plugin-packages/volcengine-ark-seedream/manifest.json index 7932f5b1b..3a4951286 100644 --- a/plugin-packages/volcengine-ark-seedream/manifest.json +++ b/plugin-packages/volcengine-ark-seedream/manifest.json @@ -5,7 +5,7 @@ "version": "2.0.0", "author": "Volcengine / 影策", "description": "Volcengine Ark Seedream Images 独立请求协议插件。", - "documentation": "# Volcengine Ark Seedream Images\n\n该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-seedream.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。\n\n完整接口见 [docs/interface.md](docs/interface.md)。\n\n---\n\n# Volcengine Ark Seedream Images 接口字段\n\n## 协议身份\n\n- 插件 ID:`volcengine-ark-seedream`。\n- Provider ID:`volcengine-ark-image`。\n- 能力:`image`。\n- 默认 Base URL:`https://ark.cn-beijing.volces.com`。\n- 鉴权驱动:`bearer`。\n- 创建:`POST /api/v3/images/generations`。\n- 生命周期:同步响应。\n\n## 配置字段\n\n| 字段 | 类型 | 必填 | 含义 |\n| --- | --- | --- | --- |\n| `apiKey` | secret | 是 | API Key |\n\n## 统一字段映射\n\n| 统一字段 | 类型 | 必填 | 上游映射 | 说明 |\n| --- | --- | --- | --- | --- |\n| `model` | string | 是 | `model` | 图片模型 ID。 |\n| `prompt` | string | 是 | `prompt` | 图片提示词。 |\n| `images` | media[] | 否 | `provider image/reference fields` | 参考图或编辑源图,role 由业务层确定。 |\n| `imageCount` | integer | 否 | `n/sample_count` | 输出数量。 |\n| `aspectRatio` | string | 否 | `size/aspect_ratio` | 比例或尺寸,语义按协议说明。 |\n| `resolution` | string | 否 | `resolution/imageSize` | 分辨率档位。 |\n| `quality` | string | 否 | `quality` | 质量档位。 |\n| `providerOptions` | object | 否 | `provider-specific fields` | 插件命名空间内的厂商扩展字段。 |\n\n## 上游请求模板逐字段清单\n\n下表由插件请求模板生成,覆盖 body、query、headers 和 multipart 文件声明中的每个字段。\n\n| 上游位置 | 值或转换表达式 |\n| --- | --- |\n| `create.method` | `\"POST\"` |\n| `create.path` | `\"/api/v3/images/generations\"` |\n| `create.contentType` | `\"application/json\"` |\n| `create.body.model` | `{\"$ref\":\"request.model\"}` |\n| `create.body.prompt` | `{\"$ref\":\"request.prompt\"}` |\n| `create.body.size` | `{\"$omitEmpty\":{\"$switch\":{\"cases\":[{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"auto\"]},\"then\":\"2k\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1:1\"]},\"then\":\"2048x2048\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"4:3\"]},\"then\":\"2304x1728\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"3:4\"]},\"then\":\"1728x2304\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"16:9\"]},\"then\":\"2560x1440\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"9:16\"]},\"then\":\"1440x2560\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"3:2\"]},\"then\":\"2496x1664\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"2:3\"]},\"then\":\"1664x2496\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"21:9\"]},\"then\":\"3024x1296\"}],\"default\":{\"$ref\":\"request.aspectRatio\"}}}}` |\n| `create.body.image` | `{\"$omitEmpty\":{\"$if\":{\"condition\":{\"$eq\":[{\"$len\":{\"$ref\":\"request.images\"}},1]},\"then\":{\"$first\":{\"$map\":{\"from\":{\"$ref\":\"request.images\"},\"as\":\"media\",\"in\":{\"$ref\":\"media.value\"}}}},\"else\":{\"$if\":{\"condition\":{\"$gt\":[{\"$len\":{\"$ref\":\"request.images\"}},1]},\"then\":{\"$map\":{\"from\":{\"$ref\":\"request.images\"},\"as\":\"media\",\"in\":{\"$ref\":\"media.value\"}}},\"else\":null}}}}}` |\n| `create.body.sequential_image_generation` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-image.sequential_image_generation\"}}` |\n| `create.body.sequential_image_generation_options` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-image.sequential_image_generation_options\"}}` |\n| `create.body.watermark` | `{\"$coalesce\":[{\"$ref\":\"request.providerOptions.volcengine-ark-image.watermark\"},{\"$ref\":\"request.watermark\"},false]}` |\n| `create.body.seed` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-image.seed\"}}` |\n| `create.body.response_format` | `{\"$coalesce\":[{\"$ref\":\"request.providerOptions.volcengine-ark-image.response_format\"},\"b64_json\"]}` |\n\n## Provider 扩展键\n\n- `providerOptions.volcengine-ark-image.response_format`\n- `providerOptions.volcengine-ark-image.seed`\n- `providerOptions.volcengine-ark-image.sequential_image_generation`\n- `providerOptions.volcengine-ark-image.sequential_image_generation_options`\n- `providerOptions.volcengine-ark-image.watermark`\n\n动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。\n\n## 响应映射逐字段清单\n\n| 映射位置 | 上游路径或转换表达式 |\n| --- | --- |\n| `response.status` | `\"succeeded\"` |\n| `response.images` | `{\"$ref\":\"response.data\"}` |\n| `response.usage` | `{\"$ref\":\"response.usage\"}` |\n| `response.errorPaths[0]` | `\"error.code\"` |\n| `response.messagePaths[0]` | `\"error.message\"` |\n\n## 响应与错误\n\n插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。\n\n## 兼容边界\n\n该包只代表上述线协议 profile;同一品牌的其他 endpoint、云区域或网关包装必须使用独立插件,不能根据模型名猜测。\n\n\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v2\",\n \"id\": \"volcengine-ark-seedream\",\n \"name\": \"Volcengine Ark Seedream Images\",\n \"version\": \"2.0.0\",\n \"author\": \"Volcengine / 影策\",\n \"description\": \"Volcengine Ark Seedream Images 独立请求协议插件。\",\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\",\n \"permissions\": [\n \"generation.run\",\n \"media.read\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"apiKey\",\n \"type\": \"secret\",\n \"label\": \"API Key\",\n \"required\": true\n }\n ]\n },\n \"contributes\": {\n \"providers\": [\n {\n \"id\": \"volcengine-ark-image\",\n \"label\": \"Volcengine Ark Seedream Images\",\n \"capabilities\": [\n \"image\"\n ],\n \"scopes\": [\n \"admin.system-channel\",\n \"user.custom-channel\",\n \"canvas\",\n \"creation\",\n \"agent\"\n ],\n \"baseUrl\": \"https://ark.cn-beijing.volces.com\",\n \"requiresPublicMediaUrls\": false,\n \"auth\": {\n \"type\": \"bearer\",\n \"field\": \"apiKey\"\n },\n \"parameters\": [\n {\n \"name\": \"model\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"model\",\n \"description\": \"图片模型 ID。\"\n },\n {\n \"name\": \"prompt\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"prompt\",\n \"description\": \"图片提示词。\"\n },\n {\n \"name\": \"images\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"provider image/reference fields\",\n \"description\": \"参考图或编辑源图,role 由业务层确定。\"\n },\n {\n \"name\": \"imageCount\",\n \"type\": \"integer\",\n \"required\": false,\n \"mapping\": \"n/sample_count\",\n \"description\": \"输出数量。\"\n },\n {\n \"name\": \"aspectRatio\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"size/aspect_ratio\",\n \"description\": \"比例或尺寸,语义按协议说明。\"\n },\n {\n \"name\": \"resolution\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"resolution/imageSize\",\n \"description\": \"分辨率档位。\"\n },\n {\n \"name\": \"quality\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"quality\",\n \"description\": \"质量档位。\"\n },\n {\n \"name\": \"providerOptions\",\n \"type\": \"object\",\n \"required\": false,\n \"mapping\": \"provider-specific fields\",\n \"description\": \"插件命名空间内的厂商扩展字段。\"\n }\n ],\n \"create\": {\n \"method\": \"POST\",\n \"path\": \"/api/v3/images/generations\",\n \"contentType\": \"application/json\",\n \"body\": {\n \"model\": {\n \"$ref\": \"request.model\"\n },\n \"prompt\": {\n \"$ref\": \"request.prompt\"\n },\n \"size\": {\n \"$omitEmpty\": {\n \"$switch\": {\n \"cases\": [\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"auto\"\n ]\n },\n \"then\": \"2k\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1:1\"\n ]\n },\n \"then\": \"2048x2048\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"4:3\"\n ]\n },\n \"then\": \"2304x1728\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"3:4\"\n ]\n },\n \"then\": \"1728x2304\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"16:9\"\n ]\n },\n \"then\": \"2560x1440\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"9:16\"\n ]\n },\n \"then\": \"1440x2560\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"3:2\"\n ]\n },\n \"then\": \"2496x1664\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"2:3\"\n ]\n },\n \"then\": \"1664x2496\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"21:9\"\n ]\n },\n \"then\": \"3024x1296\"\n }\n ],\n \"default\": {\n \"$ref\": \"request.aspectRatio\"\n }\n }\n }\n },\n \"image\": {\n \"$omitEmpty\": {\n \"$if\": {\n \"condition\": {\n \"$eq\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n 1\n ]\n },\n \"then\": {\n \"$first\": {\n \"$map\": {\n \"from\": {\n \"$ref\": \"request.images\"\n },\n \"as\": \"media\",\n \"in\": {\n \"$ref\": \"media.value\"\n }\n }\n }\n },\n \"else\": {\n \"$if\": {\n \"condition\": {\n \"$gt\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n 1\n ]\n },\n \"then\": {\n \"$map\": {\n \"from\": {\n \"$ref\": \"request.images\"\n },\n \"as\": \"media\",\n \"in\": {\n \"$ref\": \"media.value\"\n }\n }\n },\n \"else\": null\n }\n }\n }\n }\n },\n \"sequential_image_generation\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.sequential_image_generation\"\n }\n },\n \"sequential_image_generation_options\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.sequential_image_generation_options\"\n }\n },\n \"watermark\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.watermark\"\n },\n {\n \"$ref\": \"request.watermark\"\n },\n false\n ]\n },\n \"seed\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.seed\"\n }\n },\n \"response_format\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.response_format\"\n },\n \"b64_json\"\n ]\n }\n }\n },\n \"response\": {\n \"status\": \"succeeded\",\n \"images\": {\n \"$ref\": \"response.data\"\n },\n \"usage\": {\n \"$ref\": \"response.usage\"\n },\n \"errorPaths\": [\n \"error.code\"\n ],\n \"messagePaths\": [\n \"error.message\"\n ]\n }\n }\n ]\n }\n}\n```\n\n", + "documentation": "# Volcengine Ark Seedream Images\n\n该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-seedream.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。\n\n完整接口见 [docs/interface.md](docs/interface.md)。\n\n---\n\n# Volcengine Ark Seedream Images 接口字段\n\n## 协议身份\n\n- 插件 ID:`volcengine-ark-seedream`。\n- Provider ID:`volcengine-ark-image`。\n- 能力:`image`。\n- 默认 Base URL:`https://ark.cn-beijing.volces.com`。\n- 鉴权驱动:`bearer`。\n- 创建:`POST /api/v3/images/generations`。\n- 生命周期:同步响应。\n\n## 配置字段\n\n| 字段 | 类型 | 必填 | 含义 |\n| --- | --- | --- | --- |\n| `apiKey` | secret | 是 | API Key |\n\n## 统一字段映射\n\n| 统一字段 | 类型 | 必填 | 上游映射 | 说明 |\n| --- | --- | --- | --- | --- |\n| `model` | string | 是 | `model` | 图片模型 ID。 |\n| `prompt` | string | 是 | `prompt` | 图片提示词。 |\n| `images` | media[] | 否 | `provider image/reference fields` | 参考图或编辑源图,role 由业务层确定。 |\n| `imageCount` | integer | 否 | `n/sample_count` | 输出数量。 |\n| `aspectRatio` | string | 否 | `size/aspect_ratio` | 比例或尺寸,语义按协议说明。 |\n| `resolution` | string | 否 | `resolution/imageSize` | 分辨率档位。 |\n| `quality` | string | 否 | `quality` | 质量档位。 |\n| `providerOptions` | object | 否 | `provider-specific fields` | 插件命名空间内的厂商扩展字段。 |\n\n## 上游请求模板逐字段清单\n\n下表由插件请求模板生成,覆盖 body、query、headers 和 multipart 文件声明中的每个字段。\n\n| 上游位置 | 值或转换表达式 |\n| --- | --- |\n| `create.method` | `\"POST\"` |\n| `create.path` | `\"/api/v3/images/generations\"` |\n| `create.contentType` | `\"application/json\"` |\n| `create.body.model` | `{\"$ref\":\"request.model\"}` |\n| `create.body.prompt` | `{\"$ref\":\"request.prompt\"}` |\n| `create.body.size` | `{\"$omitEmpty\":{\"$switch\":{\"cases\":[{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"auto\"]},\"then\":\"2k\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1:1\"]},\"then\":\"2048x2048\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"4:3\"]},\"then\":\"2304x1728\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"3:4\"]},\"then\":\"1728x2304\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"16:9\"]},\"then\":\"2560x1440\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"9:16\"]},\"then\":\"1440x2560\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"3:2\"]},\"then\":\"2496x1664\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"2:3\"]},\"then\":\"1664x2496\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"21:9\"]},\"then\":\"3024x1296\"}],\"default\":{\"$ref\":\"request.aspectRatio\"}}}}` |\n| `create.body.image` | `{\"$omitEmpty\":{\"$if\":{\"condition\":{\"$eq\":[{\"$len\":{\"$ref\":\"request.images\"}},1]},\"then\":{\"$first\":{\"$map\":{\"from\":{\"$ref\":\"request.images\"},\"as\":\"media\",\"in\":{\"$ref\":\"media.value\"}}}},\"else\":{\"$if\":{\"condition\":{\"$gt\":[{\"$len\":{\"$ref\":\"request.images\"}},1]},\"then\":{\"$map\":{\"from\":{\"$ref\":\"request.images\"},\"as\":\"media\",\"in\":{\"$ref\":\"media.value\"}}},\"else\":null}}}}}` |\n| `create.body.sequential_image_generation` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-image.sequential_image_generation\"}}` |\n| `create.body.sequential_image_generation_options` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-image.sequential_image_generation_options\"}}` |\n| `create.body.watermark` | `{\"$coalesce\":[{\"$ref\":\"request.providerOptions.volcengine-ark-image.watermark\"},{\"$ref\":\"request.watermark\"},false]}` |\n| `create.body.seed` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.volcengine-ark-image.seed\"}}` |\n| `create.body.response_format` | `{\"$coalesce\":[{\"$ref\":\"request.providerOptions.volcengine-ark-image.response_format\"},\"b64_json\"]}` |\n\n## Provider 扩展键\n\n- `providerOptions.volcengine-ark-image.response_format`\n- `providerOptions.volcengine-ark-image.seed`\n- `providerOptions.volcengine-ark-image.sequential_image_generation`\n- `providerOptions.volcengine-ark-image.sequential_image_generation_options`\n- `providerOptions.volcengine-ark-image.watermark`\n\n动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。\n\n## 响应映射逐字段清单\n\n| 映射位置 | 上游路径或转换表达式 |\n| --- | --- |\n| `response.status` | `\"succeeded\"` |\n| `response.images` | `{\"$ref\":\"response.data\"}` |\n| `response.usage` | `{\"$ref\":\"response.usage\"}` |\n| `response.errorPaths[0]` | `\"error.code\"` |\n| `response.messagePaths[0]` | `\"error.message\"` |\n\n## 响应与错误\n\n插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。\n\n## 兼容边界\n\n官方 Ark 推理接入:Base URL 使用 /api/v3,API Key 来自方舟推理接入控制台,不可与 Agent Plan 专属 Key 混用。\n\n\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v2\",\n \"id\": \"volcengine-ark-seedream\",\n \"name\": \"Volcengine Ark Seedream Images\",\n \"version\": \"2.0.0\",\n \"author\": \"Volcengine / 影策\",\n \"description\": \"Volcengine Ark Seedream Images 独立请求协议插件。\",\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\",\n \"permissions\": [\n \"generation.run\",\n \"media.read\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"apiKey\",\n \"type\": \"secret\",\n \"label\": \"API Key\",\n \"required\": true\n }\n ]\n },\n \"contributes\": {\n \"providers\": [\n {\n \"id\": \"volcengine-ark-image\",\n \"label\": \"Volcengine Ark Seedream Images\",\n \"capabilities\": [\n \"image\"\n ],\n \"scopes\": [\n \"admin.system-channel\",\n \"user.custom-channel\",\n \"canvas\",\n \"creation\",\n \"agent\"\n ],\n \"baseUrl\": \"https://ark.cn-beijing.volces.com\",\n \"requiresPublicMediaUrls\": false,\n \"auth\": {\n \"type\": \"bearer\",\n \"field\": \"apiKey\"\n },\n \"parameters\": [\n {\n \"name\": \"model\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"model\",\n \"description\": \"图片模型 ID。\"\n },\n {\n \"name\": \"prompt\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"prompt\",\n \"description\": \"图片提示词。\"\n },\n {\n \"name\": \"images\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"provider image/reference fields\",\n \"description\": \"参考图或编辑源图,role 由业务层确定。\"\n },\n {\n \"name\": \"imageCount\",\n \"type\": \"integer\",\n \"required\": false,\n \"mapping\": \"n/sample_count\",\n \"description\": \"输出数量。\"\n },\n {\n \"name\": \"aspectRatio\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"size/aspect_ratio\",\n \"description\": \"比例或尺寸,语义按协议说明。\"\n },\n {\n \"name\": \"resolution\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"resolution/imageSize\",\n \"description\": \"分辨率档位。\"\n },\n {\n \"name\": \"quality\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"quality\",\n \"description\": \"质量档位。\"\n },\n {\n \"name\": \"providerOptions\",\n \"type\": \"object\",\n \"required\": false,\n \"mapping\": \"provider-specific fields\",\n \"description\": \"插件命名空间内的厂商扩展字段。\"\n }\n ],\n \"create\": {\n \"method\": \"POST\",\n \"path\": \"/api/v3/images/generations\",\n \"contentType\": \"application/json\",\n \"body\": {\n \"model\": {\n \"$ref\": \"request.model\"\n },\n \"prompt\": {\n \"$ref\": \"request.prompt\"\n },\n \"size\": {\n \"$omitEmpty\": {\n \"$switch\": {\n \"cases\": [\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"auto\"\n ]\n },\n \"then\": \"2k\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1:1\"\n ]\n },\n \"then\": \"2048x2048\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"4:3\"\n ]\n },\n \"then\": \"2304x1728\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"3:4\"\n ]\n },\n \"then\": \"1728x2304\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"16:9\"\n ]\n },\n \"then\": \"2560x1440\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"9:16\"\n ]\n },\n \"then\": \"1440x2560\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"3:2\"\n ]\n },\n \"then\": \"2496x1664\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"2:3\"\n ]\n },\n \"then\": \"1664x2496\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"21:9\"\n ]\n },\n \"then\": \"3024x1296\"\n }\n ],\n \"default\": {\n \"$ref\": \"request.aspectRatio\"\n }\n }\n }\n },\n \"image\": {\n \"$omitEmpty\": {\n \"$if\": {\n \"condition\": {\n \"$eq\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n 1\n ]\n },\n \"then\": {\n \"$first\": {\n \"$map\": {\n \"from\": {\n \"$ref\": \"request.images\"\n },\n \"as\": \"media\",\n \"in\": {\n \"$ref\": \"media.value\"\n }\n }\n }\n },\n \"else\": {\n \"$if\": {\n \"condition\": {\n \"$gt\": [\n {\n \"$len\": {\n \"$ref\": \"request.images\"\n }\n },\n 1\n ]\n },\n \"then\": {\n \"$map\": {\n \"from\": {\n \"$ref\": \"request.images\"\n },\n \"as\": \"media\",\n \"in\": {\n \"$ref\": \"media.value\"\n }\n }\n },\n \"else\": null\n }\n }\n }\n }\n },\n \"sequential_image_generation\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.sequential_image_generation\"\n }\n },\n \"sequential_image_generation_options\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.sequential_image_generation_options\"\n }\n },\n \"watermark\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.watermark\"\n },\n {\n \"$ref\": \"request.watermark\"\n },\n false\n ]\n },\n \"seed\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.seed\"\n }\n },\n \"response_format\": {\n \"$coalesce\": [\n {\n \"$ref\": \"request.providerOptions.volcengine-ark-image.response_format\"\n },\n \"b64_json\"\n ]\n }\n }\n },\n \"response\": {\n \"status\": \"succeeded\",\n \"images\": {\n \"$ref\": \"response.data\"\n },\n \"usage\": {\n \"$ref\": \"response.usage\"\n },\n \"errorPaths\": [\n \"error.code\"\n ],\n \"messagePaths\": [\n \"error.message\"\n ]\n }\n }\n ]\n }\n}\n```\n\n", "permissions": [ "generation.run", "media.read" diff --git a/web/src/components/video-settings-panel.tsx b/web/src/components/video-settings-panel.tsx index b6b055d42..249c77dfb 100644 --- a/web/src/components/video-settings-panel.tsx +++ b/web/src/components/video-settings-panel.tsx @@ -3,6 +3,7 @@ import { Switch } from "@/components/ui/base/switch"; import { ImageSettingsTheme } from "@/components/image-settings-panel"; import { boolConfig, isSeedanceFastModel, isSeedanceVideoConfig, normalizeSeedanceDuration, normalizeSeedanceRatio, normalizeSeedanceResolution, seedanceRatioOptions } from "@/lib/seedance-video"; +import { isVolcengineArkVideoProtocol } from "@/lib/model-protocols"; import { type CanvasTheme } from "@/lib/canvas-theme"; import { formatVideoResolutionLabel, isVideoResolutionMatch, normalizeVideoDuration, videoDimensionsForRatioAndResolution, videoResolutionComparisonKey, VIDEO_DURATION_MIN } from "@/lib/video-generation-options"; import { modelCapabilityConfigFor, resolveVideoRatioValue, resolveVideoResolutionValue, videoDurationOptions, type VideoCapabilityConfig } from "@/lib/model-capabilities"; @@ -115,7 +116,7 @@ function SeedanceVideoSettingsPanel({ config, profile, priceTiers, onConfigChang const generateAudio = boolConfig(config.videoGenerateAudio, profile.generateAudio.default); const watermark = boolConfig(config.videoWatermark, profile.watermark.default); const useArkPrivateAssets = boolConfig(config.videoArkPrivateAssetUpload, true); - const isArkSeedance = resolveModelRequestConfig(config, config.model).interfaceType === "volcengine-ark-video"; + const isArkSeedance = isVolcengineArkVideoProtocol(resolveModelRequestConfig(config, config.model).interfaceType); return ( diff --git a/web/src/lib/model-capabilities.ts b/web/src/lib/model-capabilities.ts index 5cf64edef..3f2e93493 100644 --- a/web/src/lib/model-capabilities.ts +++ b/web/src/lib/model-capabilities.ts @@ -216,7 +216,7 @@ export function defaultImageCapabilityConfig(protocol?: ModelProtocol, model = " image.responseFormat = { supported: true }; image.outputFormat = { supported: false }; image.maxOutputs = 1; - } else if (protocol === "volcengine-ark-image") { + } else if (protocol === "volcengine-ark-image" || protocol === "volcengine-ark-agent-plan-image") { image.references.maskSupported = false; image.quality.supported = false; image.transparentBackground.supported = false; @@ -320,7 +320,7 @@ export function defaultModelCapabilityConfig(protocol?: ModelProtocol, model = " video.duration = { selection: "enum", values: [4, 6, 8], default: 6 }; video.resolutions = ["720p", "1080p"]; } - if (protocol === "volcengine-ark-video" || protocol === "newapi-channel-1" || protocol === "newapi-channel-2") { + if (protocol === "volcengine-ark-video" || protocol === "volcengine-ark-agent-plan-video" || protocol === "newapi-channel-1" || protocol === "newapi-channel-2") { video.references.maxVideos = 3; video.references.maxAudios = 3; video.references.maxVideoBytes = 200 * 1024 * 1024; @@ -329,8 +329,8 @@ export function defaultModelCapabilityConfig(protocol?: ModelProtocol, model = " video.references.maxAudioDurationSeconds = 15; video.generateAudio = { supported: true, default: true }; } - if (protocol === "volcengine-ark-video" || protocol === "newapi-channel-1") video.resolutions = ["480p", "720p", "1080p"]; - if (protocol === "volcengine-ark-video") { + if (protocol === "volcengine-ark-video" || protocol === "volcengine-ark-agent-plan-video" || protocol === "newapi-channel-1") video.resolutions = ["480p", "720p", "1080p"]; + if (protocol === "volcengine-ark-video" || protocol === "volcengine-ark-agent-plan-video") { video.watermark = { supported: true, default: false }; video.operations.push("reference_to_video", "audio_to_video"); } diff --git a/web/src/lib/model-protocols.ts b/web/src/lib/model-protocols.ts index c1ac8e28c..729bd8b8b 100644 --- a/web/src/lib/model-protocols.ts +++ b/web/src/lib/model-protocols.ts @@ -9,8 +9,16 @@ export function protocolGroups(protocols: ModelProtocolDefinition[]) { export function modelProtocolDefinition(value: string | undefined, definitions: ModelProtocolDefinition[] = []) { return definitions.find((item) => item.value === value); } export function modelProtocolLabel(value: string | undefined, definitions: ModelProtocolDefinition[] = []) { return modelProtocolDefinition(value, definitions)?.label || (value ? value : "未安装协议"); } export function modelProtocolCapability(value: string | undefined, definitions: ModelProtocolDefinition[] = []) { return modelProtocolDefinition(value, definitions)?.capability; } +export function isVolcengineArkImageProtocol(protocol?: string) { + return protocol === "volcengine-ark-image" || protocol === "volcengine-ark-agent-plan-image"; +} + +export function isVolcengineArkVideoProtocol(protocol?: string) { + return protocol === "volcengine-ark-video" || protocol === "volcengine-ark-agent-plan-video"; +} + export function modelProtocolSupportsTokenBilling(capability?: string, protocol?: string) { - return capability === "text" || (capability === "video" && protocol === "volcengine-ark-video"); + return capability === "text" || (capability === "video" && isVolcengineArkVideoProtocol(protocol)); } export function protocolForModelCatalog(_endpointTypes: string[] = []): ModelProtocol | undefined { diff --git a/web/src/lib/plugins/builtin/prompt-optimizer.ts b/web/src/lib/plugins/builtin/prompt-optimizer.ts index 1073869c0..39b089524 100644 --- a/web/src/lib/plugins/builtin/prompt-optimizer.ts +++ b/web/src/lib/plugins/builtin/prompt-optimizer.ts @@ -136,7 +136,7 @@ function resolveModelAdaptationProfile(input: PromptOptimizationInput): ModelAda avoid: ["不要添加 SD 权重、Midjourney 参数或模型未确认支持的控制语法。"], }; } - if (containsAny(model, ["seedream", "jimeng", "doubao", "volcengine-ark-image", "volcengine-jimeng-image"])) { + if (containsAny(model, ["seedream", "jimeng", "doubao", "volcengine-ark-image", "volcengine-ark-agent-plan-image", "volcengine-jimeng-image"])) { return { id: "seedream-image", label: "Seedream / 即梦 / 火山图片模型", @@ -184,7 +184,7 @@ function resolveModelAdaptationProfile(input: PromptOptimizationInput): ModelAda return genericImageProfile; } - if (containsAny(model, ["seedance", "doubao-seedance", "volcengine-ark-video", "volcengine-jimeng-video", "jimeng-video"])) { + if (containsAny(model, ["seedance", "doubao-seedance", "volcengine-ark-video", "volcengine-ark-agent-plan-video", "volcengine-jimeng-video", "jimeng-video"])) { return { id: "seedance-video", label: "Seedance / 即梦视频模型", diff --git a/web/src/services/api/image.ts b/web/src/services/api/image.ts index 769fb82f1..7ab3978ea 100644 --- a/web/src/services/api/image.ts +++ b/web/src/services/api/image.ts @@ -6,6 +6,7 @@ import type { ReferenceImage } from "@/types/image"; import { withOpenAIPromptCacheKey } from "@/lib/openai-prompt-cache"; import { modelCapabilityConfigFor, normalizeImageValue } from "@/lib/model-capabilities"; import { buildGeminiImageGenerationConfig, parseGeminiImageDataUrl, type GeminiImageGenerationConfig } from "@/lib/gemini-image"; +import { isVolcengineArkImageProtocol } from "@/lib/model-protocols"; import { aiApiUrl, aiHeaders, imageChannelTransport, postChannelJSON, postGeminiJSON, postVolcengineArkImage } from "@/services/api/image-transport"; const IMAGE_OUTPUT_FORMAT = "png"; @@ -94,7 +95,7 @@ export async function requestGeneration(config: AiConfig, prompt: string, option } const quality = imageProfile.quality.supported && normalizedImage.quality !== "auto" ? normalizeQuality(normalizedImage.quality) || normalizedImage.quality : undefined; const requestSize = resolveImageRequestSize(imageProfile, quality, normalizedImage.size); - const isVolcengineArk = requestConfig.interfaceType === "volcengine-ark-image"; + const isVolcengineArk = isVolcengineArkImageProtocol(requestConfig.interfaceType); const normalizedRequestSize = requestSize?.parameter === "size" && isVolcengineArk ? { ...requestSize, value: normalizeVolcengineArkImageSize(requestSize.value)! } : requestSize; try { const payload = isVolcengineArk @@ -174,7 +175,7 @@ export async function requestEdit(config: AiConfig, prompt: string, references: throw new Error(readAxiosError(error, "Grok 图片编辑失败")); } } - if (requestConfig.interfaceType === "volcengine-ark-image") { + if (isVolcengineArkImageProtocol(requestConfig.interfaceType)) { if (mask) throw new Error("火山方舟图片协议不支持蒙版编辑,请移除蒙版后重试"); const quality = imageProfile.quality.supported && normalizedImage.quality !== "auto" ? normalizeQuality(normalizedImage.quality) || normalizedImage.quality : undefined; const sizeRequest = resolveImageRequestSize(imageProfile, quality, normalizedImage.size); diff --git a/web/src/services/api/video-provider-seedance.ts b/web/src/services/api/video-provider-seedance.ts index abf255c62..757b35702 100644 --- a/web/src/services/api/video-provider-seedance.ts +++ b/web/src/services/api/video-provider-seedance.ts @@ -1,4 +1,5 @@ import { modelCapabilityConfigFor } from "@/lib/model-capabilities"; +import { isVolcengineArkVideoProtocol } from "@/lib/model-protocols"; import { boolConfig, buildSeedancePromptText, isArkPlanBaseUrl, isSeedanceVideoConfig, normalizeSeedanceDuration, normalizeSeedanceRatio, normalizeSeedanceResolution, seedanceVideoReferenceError, SEEDANCE_REFERENCE_LIMITS } from "@/lib/seedance-video"; import { getResourceOSSUrl } from "@/services/api/resources"; import { getMediaBlob } from "@/services/file-storage"; @@ -13,13 +14,13 @@ import type { VideoProviderDeps } from "./video-provider-deps"; import { hasExplicitVideoFrames, resolveVideoImageReferences } from "./video-reference-roles"; export function isSeedanceConfig(config: ResolvedAiConfig) { - return config.interfaceType === "volcengine-ark-video" || isSeedanceVideoConfig(config); + return isVolcengineArkVideoProtocol(config.interfaceType) || isSeedanceVideoConfig(config); } export async function createSeedanceTask(deps: VideoProviderDeps, config: ResolvedAiConfig, model: string, prompt: string, references: ReferenceImage[], videoReferences: ReferenceVideo[], audioReferences: ReferenceAudio[], options?: RequestOptions): Promise { assertSeedanceVideoReferences(videoReferences); assertSeedanceAudioReferences(audioReferences); - const isVolcengineArk = config.interfaceType === "volcengine-ark-video"; + const isVolcengineArk = isVolcengineArkVideoProtocol(config.interfaceType); const payload = isVolcengineArk || isArkPlanBaseUrl(config.baseUrl) ? await buildSeedanceAgentPlanPayload(config, model, prompt, references, videoReferences, audioReferences, deps, options) : await buildSeedanceVideosPayload(config, model, prompt, references, videoReferences, audioReferences, deps, options); @@ -77,15 +78,15 @@ function assertSeedanceAudioReferences(audioReferences: ReferenceAudio[]) { } function seedanceApiUrl(config: ResolvedAiConfig, taskId?: string) { - if (config.interfaceType === "volcengine-ark-video" || isArkPlanBaseUrl(config.baseUrl)) return buildApiUrl(config.baseUrl, `/contents/generations/tasks${taskId ? `/${encodeURIComponent(taskId)}` : ""}`); + if (isVolcengineArkVideoProtocol(config.interfaceType) || isArkPlanBaseUrl(config.baseUrl)) return buildApiUrl(config.baseUrl, `/contents/generations/tasks${taskId ? `/${encodeURIComponent(taskId)}` : ""}`); return buildApiUrl(config.baseUrl, `/videos${taskId ? `/${encodeURIComponent(taskId)}` : ""}`); } async function buildSeedanceAgentPlanPayload(config: ResolvedAiConfig, model: string, prompt: string, references: ReferenceImage[], videoReferences: ReferenceVideo[], audioReferences: ReferenceAudio[], deps: VideoProviderDeps, options?: RequestOptions) { if (audioReferences.length && !references.length && !videoReferences.length) { - throw new Error(config.interfaceType === "volcengine-ark-video" ? "火山方舟全模态参考不支持纯音频或文本+音频,请同时添加参考图片或参考视频" : "Seedance 参考音频不能单独使用,请同时添加参考图或参考视频"); + throw new Error(isVolcengineArkVideoProtocol(config.interfaceType) ? "火山方舟全模态参考不支持纯音频或文本+音频,请同时添加参考图片或参考视频" : "Seedance 参考音频不能单独使用,请同时添加参考图或参考视频"); } - const content = config.interfaceType === "volcengine-ark-video" + const content = isVolcengineArkVideoProtocol(config.interfaceType) ? await buildVolcengineArkContent(prompt, references, videoReferences, audioReferences, options) : await buildSeedanceContent(config, prompt, references, videoReferences, audioReferences, deps, options); if (!content.length) throw new Error("请输入视频提示词,或连接参考图片/视频/音频"); diff --git a/web/src/stores/use-config-store.ts b/web/src/stores/use-config-store.ts index 8e71ad05c..626ef91a4 100644 --- a/web/src/stores/use-config-store.ts +++ b/web/src/stores/use-config-store.ts @@ -912,6 +912,7 @@ export function defaultBaseUrlForApiFormat(apiFormat: ApiCallFormat) { export function defaultBaseUrlForChannelInterface(interfaceType?: ChannelInterfaceType) { if (interfaceType === "gemini-veo" || interfaceType === "gemini-image") return GEMINI_BASE_URL; if (interfaceType === "novita-video") return "https://api.novita.ai/v3"; + if (interfaceType === "volcengine-ark-agent-plan-image" || interfaceType === "volcengine-ark-agent-plan-video") return "https://ark.cn-beijing.volces.com/api/plan/v3"; if (interfaceType === "volcengine-ark-image" || interfaceType === "volcengine-ark-video") return "https://ark.cn-beijing.volces.com/api/v3"; if (interfaceType === "volcengine-jimeng-image" || interfaceType === "volcengine-jimeng-video") return "https://visual.volcengineapi.com"; if (interfaceType === "minimax-video") return "https://api.minimaxi.com"; diff --git a/web/test/model-protocols.test.ts b/web/test/model-protocols.test.ts index 6cccde9f7..24a2ddad2 100644 --- a/web/test/model-protocols.test.ts +++ b/web/test/model-protocols.test.ts @@ -6,8 +6,10 @@ describe("model protocol Token billing", () => { test("supports text models and Volcengine Ark video only", () => { expect(modelProtocolSupportsTokenBilling("text", "chat-completion")).toBe(true); expect(modelProtocolSupportsTokenBilling("video", "volcengine-ark-video")).toBe(true); + expect(modelProtocolSupportsTokenBilling("video", "volcengine-ark-agent-plan-video")).toBe(true); expect(modelProtocolSupportsTokenBilling("video", "volcengine-jimeng-video")).toBe(false); expect(modelProtocolSupportsTokenBilling("video", "newapi")).toBe(false); expect(modelProtocolSupportsTokenBilling("image", "volcengine-ark-image")).toBe(false); + expect(modelProtocolSupportsTokenBilling("image", "volcengine-ark-agent-plan-image")).toBe(false); }); });