feat(plugins): 方舟 Agent Plan - 合并 PR #527 图片视频插件并阻止图片误用 asset://
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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}}
|
||||
@@ -711,6 +711,8 @@ func mediaReferencesFromManifestValue(value any, kind string, ephemeral bool) []
|
||||
if reference.DataURL == "" {
|
||||
reference.DataURL = firstString(typed, "data_url", "b64_json")
|
||||
}
|
||||
// OpenAI / Ark 等渠道常直接返回裸 b64_json;声明式结果下载要求 data URL。
|
||||
reference.DataURL = normalizeManifestInlineDataURL(reference.DataURL, reference.Kind, firstString(typed, "output_format", "mime_type", "mimeType"))
|
||||
if reference.URL != "" || reference.DataURL != "" {
|
||||
result = append(result, reference)
|
||||
}
|
||||
@@ -719,6 +721,50 @@ func mediaReferencesFromManifestValue(value any, kind string, ephemeral bool) []
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeManifestInlineDataURL(value, kind, formatHint string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || strings.HasPrefix(value, "data:") {
|
||||
return value
|
||||
}
|
||||
return "data:" + manifestInlineMediaMIME(kind, formatHint) + ";base64," + value
|
||||
}
|
||||
|
||||
func manifestInlineMediaMIME(kind, formatHint string) string {
|
||||
hint := strings.ToLower(strings.TrimSpace(formatHint))
|
||||
switch hint {
|
||||
case "image/png", "image/jpeg", "image/webp", "image/gif", "audio/mpeg", "audio/wav", "audio/ogg", "video/mp4", "video/webm":
|
||||
return hint
|
||||
case "image/jpg":
|
||||
return "image/jpeg"
|
||||
case "audio/mp3":
|
||||
return "audio/mpeg"
|
||||
case "png":
|
||||
return "image/png"
|
||||
case "jpeg", "jpg":
|
||||
return "image/jpeg"
|
||||
case "webp":
|
||||
return "image/webp"
|
||||
case "gif":
|
||||
return "image/gif"
|
||||
case "mp3", "mpeg":
|
||||
return "audio/mpeg"
|
||||
case "wav":
|
||||
return "audio/wav"
|
||||
case "mp4":
|
||||
return "video/mp4"
|
||||
case "webm":
|
||||
return "video/webm"
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(kind)) {
|
||||
case "audio":
|
||||
return "audio/mpeg"
|
||||
case "video":
|
||||
return "video/mp4"
|
||||
default:
|
||||
return "image/png"
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
manifestMDImageRegex = regexp.MustCompile(`!\[[^\]]*\]\(([^)\s]+)\)`)
|
||||
manifestHTMLVideoRegex = regexp.MustCompile(`(?i)<video[^>]+src=['"]([^'"]+)['"]`)
|
||||
|
||||
@@ -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 {
|
||||
@@ -754,3 +784,27 @@ func TestOfficialOpenAIAudioSpeedDefaultsInvalidAndZeroValues(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOfficialArkSeedreamParsesB64JSONAsDataURL(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
packageName, providerID string
|
||||
}{
|
||||
{"volcengine-ark-seedream.yingce-plugin", "volcengine-ark-image"},
|
||||
{"volcengine-ark-agent-plan-seedream.yingce-plugin", "volcengine-ark-agent-plan-image"},
|
||||
} {
|
||||
t.Run(tc.providerID, func(t *testing.T) {
|
||||
adapter := officialPackageAdapter(t, tc.packageName, tc.providerID)
|
||||
result, err := adapter.ParseCreate(context.Background(), []byte(`{"created":1,"data":[{"b64_json":"aW1hZ2U=","output_format":"jpeg","size":"1824x1024"}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Result == nil || len(result.Result.Images) != 1 {
|
||||
t.Fatalf("result = %#v", result.Result)
|
||||
}
|
||||
dataURL := result.Result.Images[0].DataURL
|
||||
if dataURL != "data:image/jpeg;base64,aW1hZ2U=" {
|
||||
t.Fatalf("DataURL = %q, want jpeg data URL from b64_json + output_format", dataURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user