feat(plugins): 方舟 Agent Plan - 新增图片与视频协议插件
独立 /api/plan/v3 Seedream、Seedance 插件,并同步宿主能力默认值与渠道识别,避免与官方 /api/v3 凭证混用。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
11
backend/internal/model/volcengine_ark.go
Normal file
11
backend/internal/model/volcengine_ark.go
Normal file
@@ -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
|
||||
}
|
||||
24
backend/internal/model/volcengine_ark_test.go
Normal file
24
backend/internal/model/volcengine_ark_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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 <AGENT_PLAN_API_KEY>
|
||||
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}}
|
||||
@@ -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 <AGENT_PLAN_API_KEY>
|
||||
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}}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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://`。后端单测已覆盖。需浏览器验收:
|
||||
|
||||
@@ -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 包装 |
|
||||
|
||||
@@ -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: [
|
||||
|
||||
BIN
plugin-packages/volcengine-ark-agent-plan-seedance.yingce-plugin
Normal file
BIN
plugin-packages/volcengine-ark-agent-plan-seedance.yingce-plugin
Normal file
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
# Volcengine Ark Agent Plan Seedance
|
||||
|
||||
该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-agent-plan-seedance.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。
|
||||
|
||||
完整接口见 [docs/interface.md](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 混用。
|
||||
|
||||
<!-- YINGCE_MANIFEST_CONTRACT_START -->
|
||||
## 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
<!-- YINGCE_MANIFEST_CONTRACT_END -->
|
||||
431
plugin-packages/volcengine-ark-agent-plan-seedance/manifest.json
Normal file
431
plugin-packages/volcengine-ark-agent-plan-seedance/manifest.json
Normal file
File diff suppressed because one or more lines are too long
BIN
plugin-packages/volcengine-ark-agent-plan-seedream.yingce-plugin
Normal file
BIN
plugin-packages/volcengine-ark-agent-plan-seedream.yingce-plugin
Normal file
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
# Volcengine Ark Agent Plan Seedream Images
|
||||
|
||||
该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-agent-plan-seedream.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。
|
||||
|
||||
完整接口见 [docs/interface.md](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 混用。
|
||||
|
||||
<!-- YINGCE_MANIFEST_CONTRACT_START -->
|
||||
## 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
<!-- YINGCE_MANIFEST_CONTRACT_END -->
|
||||
332
plugin-packages/volcengine-ark-agent-plan-seedream/manifest.json
Normal file
332
plugin-packages/volcengine-ark-agent-plan-seedream/manifest.json
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
5
plugin-packages/volcengine-ark-seedance/README.md
Normal file
5
plugin-packages/volcengine-ark-seedance/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Volcengine Ark Seedance
|
||||
|
||||
该目录是独立官方协议插件源码。后端从生成的 `volcengine-ark-seedance.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。
|
||||
|
||||
完整接口见 [docs/interface.md](docs/interface.md)。
|
||||
527
plugin-packages/volcengine-ark-seedance/docs/interface.md
Normal file
527
plugin-packages/volcengine-ark-seedance/docs/interface.md
Normal file
@@ -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 来自方舟推理接入控制台。
|
||||
|
||||
<!-- YINGCE_MANIFEST_CONTRACT_START -->
|
||||
## 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
<!-- YINGCE_MANIFEST_CONTRACT_END -->
|
||||
431
plugin-packages/volcengine-ark-seedance/manifest.json
Normal file
431
plugin-packages/volcengine-ark-seedance/manifest.json
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -74,7 +74,7 @@
|
||||
|
||||
## 兼容边界
|
||||
|
||||
该包只代表上述线协议 profile;同一品牌的其他 endpoint、云区域或网关包装必须使用独立插件,不能根据模型名猜测。
|
||||
官方 Ark 推理接入:Base URL 使用 /api/v3,API Key 来自方舟推理接入控制台,不可与 Agent Plan 专属 Key 混用。
|
||||
|
||||
<!-- YINGCE_MANIFEST_CONTRACT_START -->
|
||||
## Manifest 完整接口定义
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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 (
|
||||
<ImageSettingsTheme theme={theme}>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 / 即梦视频模型",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<VideoGenerationTask> {
|
||||
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("请输入视频提示词,或连接参考图片/视频/音频");
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user