From ebc18590204e3020dc9597c5bf33b269332d977d Mon Sep 17 00:00:00 2001 From: zhuchi Date: Mon, 14 Sep 2026 21:05:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(provider):=20=E6=A8=A1=E5=9E=8B=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=20-=20=E6=96=B0=E5=A2=9E=E7=99=BE=E7=82=BC=E5=A4=9A?= =?UTF-8?q?=E6=A8=A1=E6=80=81=E5=9B=BE=E5=83=8F=E5=8D=8F=E8=AE=AE=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../protocol/dashscope_qwen_image_test.go | 174 ++++++ docs/content/docs/progress/pending-test.mdx | 9 + .../dashscope-qwen-image.yingce-plugin | Bin 0 -> 10043 bytes .../dashscope-qwen-image/README.md | 5 + .../dashscope-qwen-image/docs/interface.md | 518 ++++++++++++++++++ .../dashscope-qwen-image/manifest.json | 426 ++++++++++++++ plugin-packages/generate-catalog.mjs | 48 ++ 7 files changed, 1180 insertions(+) create mode 100644 backend/internal/protocol/dashscope_qwen_image_test.go create mode 100644 plugin-packages/dashscope-qwen-image.yingce-plugin create mode 100644 plugin-packages/dashscope-qwen-image/README.md create mode 100644 plugin-packages/dashscope-qwen-image/docs/interface.md create mode 100644 plugin-packages/dashscope-qwen-image/manifest.json diff --git a/backend/internal/protocol/dashscope_qwen_image_test.go b/backend/internal/protocol/dashscope_qwen_image_test.go new file mode 100644 index 000000000..8c1173d48 --- /dev/null +++ b/backend/internal/protocol/dashscope_qwen_image_test.go @@ -0,0 +1,174 @@ +package protocol + +import ( + "context" + "testing" +) + +// Qwen-Image 3.0 / Wan 2.7 图像属于百炼多模态模型。官方要求走 image-generation / +// multimodal-generation 端点;用 OpenAI 图片端点会被网关判为“模型与端点不匹配”, +// 在官方错误码里表现为 url error。参考图只能放在 input.messages[].content[].image。 +func TestDashscopeQwenImageUsesMultimodalEndpoint(t *testing.T) { + adapter := officialPackageAdapter(t, "dashscope-qwen-image.yingce-plugin", "dashscope-qwen-image") + create, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{ + Model: "qwen-image-3.0-pro", Prompt: "灰色圆点", AspectRatio: "3:4", ImageCount: 1, + }}) + if err != nil { + t.Fatal(err) + } + if create.Method != "POST" || create.Path != "/api/v1/services/aigc/image-generation/generation" { + t.Fatalf("create = %#v", create) + } + if create.Headers["X-DashScope-Async"] != "enable" { + t.Fatalf("create headers = %#v", create.Headers) + } + // 原生端点是域名根下的绝对路径,不在 compatible-mode 之下;originPath 让请求路径 + // 替换 Base URL 的路径段,渠道继续沿用 .../compatible-mode/v1 也能命中正确地址。 + if !create.OriginPath { + t.Fatal("create 必须声明 originPath") + } + + body := manifestTestBody(t, create) + if body["model"] != "qwen-image-3.0-pro" { + t.Fatalf("model = %#v", body["model"]) + } + content := dashscopeQwenImageContent(t, body) + if len(content) != 1 { + t.Fatalf("文生图 content = %#v, 期望只有 text 一项", content) + } + entry, _ := content[0].(map[string]any) + if entry["text"] != "灰色圆点" { + t.Fatalf("text 项 = %#v", entry) + } + + parameters, _ := body["parameters"].(map[string]any) + if parameters["size"] != "960*1280" { + t.Fatalf("size = %#v, 期望 960*1280(DashScope 用星号而非字母 x)", parameters["size"]) + } + if parameters["enable_thinking"] != false { + t.Fatalf("enable_thinking = %#v, 官方要求非流式调用必须为 false", parameters["enable_thinking"]) + } + if parameters["n"] != float64(1) { + t.Fatalf("n = %#v, 期望 1", parameters["n"]) + } +} + +func TestDashscopeQwenImagePutsReferencesInMessageContent(t *testing.T) { + adapter := officialPackageAdapter(t, "dashscope-qwen-image.yingce-plugin", "dashscope-qwen-image") + create, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{ + Model: "qwen-image-3.0-pro", Prompt: "换装", AspectRatio: "1:1", ImageCount: 2, + Images: []MediaReference{ + {DataURL: "data:image/png;base64,QUJD", Role: "reference_image", Order: 2}, + {URL: "https://cdn.example/anchor.png", Role: "edit_source", Order: 1}, + }, + }}) + if err != nil { + t.Fatal(err) + } + + content := dashscopeQwenImageContent(t, manifestTestBody(t, create)) + if len(content) != 3 { + t.Fatalf("图生图 content = %#v, 期望 2 张图 + 1 条 text", content) + } + // order 决定顺序:先 order=1 的参考图,再 order=2;text 永远排在最后。 + first, _ := content[0].(map[string]any) + if first["image"] != "https://cdn.example/anchor.png" { + t.Fatalf("第一项 = %#v", first) + } + second, _ := content[1].(map[string]any) + if second["image"] != "data:image/png;base64,QUJD" { + t.Fatalf("第二项 = %#v, 有 dataUrl 时必须优先 Base64(大陆地域无法回源站外图片)", second) + } + third, _ := content[2].(map[string]any) + if third["text"] != "换装" { + t.Fatalf("末项 = %#v", third) + } + + parameters, _ := manifestTestBody(t, create)["parameters"].(map[string]any) + if parameters["n"] != float64(2) { + t.Fatalf("n = %#v, 期望 2", parameters["n"]) + } + if parameters["size"] != "1024*1024" { + t.Fatalf("size = %#v", parameters["size"]) + } +} + +func TestDashscopeQwenImageOmitsUnregisteredSize(t *testing.T) { + adapter := officialPackageAdapter(t, "dashscope-qwen-image.yingce-plugin", "dashscope-qwen-image") + create, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{ + Model: "qwen-image-3.0-pro", Prompt: "circle", AspectRatio: "2880x2880", + }}) + if err != nil { + t.Fatal(err) + } + parameters, _ := manifestTestBody(t, create)["parameters"].(map[string]any) + if _, present := parameters["size"]; present { + t.Fatalf("parameters = %#v, 未登记的档位必须省略 size 交由模型自动推荐", parameters) + } +} + +func TestDashscopeQwenImageLifecycleReadsMultimodalChoices(t *testing.T) { + adapter := officialPackageAdapter(t, "dashscope-qwen-image.yingce-plugin", "dashscope-qwen-image") + created, err := adapter.ParseCreate(context.Background(), []byte(`{"output":{"task_id":"task-qwen-1","task_status":"PENDING"},"request_id":"req-1"}`)) + if err != nil { + t.Fatal(err) + } + if created.TaskID != "task-qwen-1" { + t.Fatalf("taskId = %q", created.TaskID) + } + + poll, err := adapter.BuildPoll(context.Background(), PollContext{TaskID: created.TaskID}) + if err != nil { + t.Fatal(err) + } + if poll.Method != "GET" || poll.Path != "/api/v1/tasks/task-qwen-1" { + t.Fatalf("poll = %#v", poll) + } + if !poll.OriginPath { + t.Fatal("poll 必须声明 originPath") + } + + // 图片藏在 output.choices[0].message.content[].image,这是多模态端点与 wanx 的差异所在。 + completed, err := adapter.ParsePoll(context.Background(), PollContext{TaskID: created.TaskID}, []byte(`{"output":{"task_id":"task-qwen-1","task_status":"SUCCEEDED","choices":[{"finish_reason":"stop","message":{"role":"assistant","content":[{"image":"https://dashscope-result.example/a.png","type":"image"}]}}]},"usage":{"output_image_count":1},"request_id":"req-2"}`)) + if err != nil { + t.Fatal(err) + } + if completed.Status != StatusSucceeded { + t.Fatalf("status = %q, want %q", completed.Status, StatusSucceeded) + } + if completed.Result == nil || len(completed.Result.Images) != 1 { + t.Fatalf("result = %#v", completed.Result) + } + if completed.Result.Images[0].URL != "https://dashscope-result.example/a.png" { + t.Fatalf("images = %#v", completed.Result.Images) + } +} + +func TestDashscopeQwenImageSurfacesFailureMessage(t *testing.T) { + adapter := officialPackageAdapter(t, "dashscope-qwen-image.yingce-plugin", "dashscope-qwen-image") + failed, err := adapter.ParsePoll(context.Background(), PollContext{TaskID: "task-qwen-1"}, []byte(`{"output":{"task_id":"task-qwen-1","task_status":"FAILED","code":"InvalidParameter","message":"url error, please check url!"},"request_id":"req-3"}`)) + if err != nil { + t.Fatal(err) + } + if failed.Status != StatusFailed { + t.Fatalf("status = %q, want %q", failed.Status, StatusFailed) + } + if failed.Message == "" { + t.Fatal("上游失败必须保留错误信息,不能被包装成成功") + } +} + +func dashscopeQwenImageContent(t *testing.T, body map[string]any) []any { + t.Helper() + input, _ := body["input"].(map[string]any) + messages, _ := input["messages"].([]any) + if len(messages) != 1 { + t.Fatalf("messages = %#v", messages) + } + message, _ := messages[0].(map[string]any) + if message["role"] != "user" { + t.Fatalf("role = %#v", message["role"]) + } + content, _ := message["content"].([]any) + return content +} diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index cd8895e7b..542245713 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -5,6 +5,15 @@ description: 当前项目已实现功能的手工验证清单与未确认边界 # 待测试 +## 百炼多模态图像协议插件(dashscope-qwen-image) + +- 新增官方协议包 `plugin-packages/dashscope-qwen-image.yingce-plugin`,解决 `qwen-image-3.0-pro` / `wan2.7-image-pro` 无法调用的问题:这两个模型属于百炼多模态模型,走 OpenAI 图片端点会被网关判为“模型与端点不匹配”,官方错误码表现为 `url error, please check url`。 +- 协议形态:`POST /api/v1/services/aigc/image-generation/generation`(`X-DashScope-Async: enable`)+ 轮询 `GET /api/v1/tasks/{taskId}`;参考图放在 `input.messages[].content[].image`(优先 Base64 data URL),不使用 `/v1/images/edits` 的 multipart。 +- 已由单元测试覆盖:端点与异步头、文生图只发 text、图生图按 order 排序并优先 Base64、`size` 采用 DashScope 的星号格式且未登记档位省略、`enable_thinking` 固定 false、响应从 `output.choices[0].message.content[].image` 取图、上游失败保留错误信息。`go test ./internal/protocol/` 与 `go build ./...` 通过。 +- 待真实账号验收:用北京地域 Key 走一次文生图与一次图生图(换装),确认出图成功、参考图不报 url error。 +- 已知边界:协议用 `originPath` 声明请求路径,请求路径会替换渠道 Base URL 的路径段,因此渠道继续沿用 `.../compatible-mode/v1` 也能命中原生端点(`.../api/v1/services/...`);只有域名需要正确。协议把 `size` 映射为 1024 级竖版/方形档位,未登记的档位会被省略并交由模型自动推荐(官方要求像素面积 512*512~2048*2048)。 +- 尚未处理:AI 模特页仍按“带参考图即走 `/images/edits`”的通用规则分支,未按协议能力判断;换成该插件后两阶段派生能否直接复用需实测确认。 + ## 云端 Agent 行为、能力和偏好分层(待真实环境验收) - [x] 系统/媒体行为资源由后端发布物加载,运行快照记录策略和能力版本/哈希;用户 → 项目 → 画布偏好经归属校验和独立 revision CAS,合并 revision 固定到新轮次,旧轮次保留原快照。system prompt 只注入偏好清单,正文由 `agent_profile_read` 从固定快照按层读取。工具 schema、节点类型、可写字段和画布读取来自服务端节点能力 registry;收费生成还需实际媒体任务适配器。写入、审批与预算仍以服务端强校验为准。 diff --git a/plugin-packages/dashscope-qwen-image.yingce-plugin b/plugin-packages/dashscope-qwen-image.yingce-plugin new file mode 100644 index 0000000000000000000000000000000000000000..9955f110ebc5e25e30b4027b390b608bfceb71d6 GIT binary patch literal 10043 zcmZ{KV~{Q|)8!aYN+qP}{&fJ+hwr$&f#B0-K0{TuB0oeu2fF- zsiP<1Ox^2T12n+jCdw^_2WJM4CtSsEG8r>Cq{2)s-o^FMLP{8Kg}Si21Tnd zr827lr7)>-GbtylaJc7P?|*u~cAj&U{boD-cD!kmQ+ws#VL#kx#gqH=)T`NgrS)Az z(b>wk!M6JvJUjv?r$F(*XVCpMcGYvNawKq`cH^raDJ(2)>?F=K>bMEGTjbr8jjQ)g zPWL(;EAIg3Dn3p3g8rr8qn+zc=UMM@{m#dy;QqkB#_>F~Y&FAi?cd|`wa~JB?6X2e zYI=Z?s!2xKWozlZw*KyV^!MjHU0sEhN8ZeiL#^%k($7c(S_jo|`7s6h<(WwuDLQEx zS^1?2xrn7c#Cb3)a3ID1krabQy@weDAfRb!ARwflq?kIGxG-4RyP7$h8=08>BbXj$^u^oHrRP<%@ShvT7SiX?8A^W^!4%V7cbj5GmbcD#* zW~O1o5yVVXCLzIU4kW^x0Id=WafOdiol+ljRu*ijyuj8obpN`;(}m+og0>n20ebG%Pe?$^H@8roYa-5kafB#u0fcPc*Mg3GsH;EJv1VsFlqrg*7Zs? zlY2Y2EV=g45lLWFb1{Q9WYuNWnm8k_t@ zB`XO4)dU!W6#@f~6u82tk71|3o1O~dJQPZK*SJXCQul5G#CV7g3pPo*SkDb_p%)x224odD%l4H zwI6WQj{>$G!}}kk(BoRTO7VH;hmXgW>DOrSlkbL)!a7*;4=E<$WHhEqh>W<9WRy^z zh?KC*h67Yya7rq$0xWT>P8Uqp$8Fqt3vM>3_{k{;{L64fhC7?@R--H`nh94D1Y_t0 z3%+YP3sh^hU`AGv>Lm`~{`O~#j(1_f&SnpRbvx@^GqqNwNk6i3el2M73;F-#SH ziMqZpvUu}^lD&99-7C?Kh4W4qdQz7_N$D%cu?s-h_H50gmn`jt^}GmfWGO{D*ex59 zF!wC_Il>eAl@vwK;Jgdis0d)v*bMgrQ%!lc;*~KH8|nA=bE=0SD=A8+fjSI_>J3Qg zV;P4~JUQdb#{VueR650yS1yEd@nyzBA+}vMIHdNRRh-?r+4cE1f{eez%?Z6NZ=_`; zqPuR9KuE44%@|{b#r{yeyhV5+D9%b|62Y^Kn`jW}NlGN{S*()qVw8T~<`(N1L>Mz+_07vI!r{n1ZcuZ6$nK*kgUXMm1JT&8NFYS&AH{i_W}lI==9#Y zHiQ2g^oLj|H&%rVUM{zi-`v)l)W*<^PG;YO)d(4e3Wk8g=RxF?Iiuc#VZolCdwDn2 zEq#aLe~%d{I*qRPsaa0-(Y*B1J=wJstKg%P(CQja7=y!(iP9eZO6$2e zbBb1zklEVEg8A?yLITTuVoMt;Icx{fEf|=pGJ9D?IW2$yF?E)kh-&CW*G2Q!Q+qbB z{Kh3~S1MFgecnEIv03K@d!|(m|Z<9rq5k3`7HLQz36d+F*&Bx{R#qb5YY_ zWmaCaw$zQ);H^jIMsK}nB4epb*s@y^7YiCfv%LK- zV{vyex{qQ4L0n8fG6tRaRaPGqsX)z~6bK_#w6!}WV#bI)XpWuTIi12l#G9K@&1ak|_VubERX$H(5* z9s=Y7qEb%2!7jM(ukfU*u;T#KT%`mhxMo$h+CMWXBERd1r_aa%S#`Reg`7{}Eq?Xq zT)`(es|gcvBN@6YV%srq)$wze4O^b`^VrcvfgSYj-r7TnrEJ`>83OZ$@3jRNJf>-Q zmO|R-V8Qr@9$7a#z3-)GhUO1SuSUBr9kX`WBQ{BXi>_&_{l@hd+H_yD&M4goRQIS5 zyiY5B#MxYelQm?oMktS`NlGnl!e@(zM>)Pu7gg+MeK#a3v*u_uH-}i+8r)=Zcor=? zf7x6gu@v~lJ=UW16NiM2<>Td1Tl_mSuF**RWb|?p9rIVxB6N?-^geqltmbB2;X!D_ zLY_&}OI;PNM$0Y&k(`N2KacLE30ti4Vzd37F(oTqI=Z-joN|9!0{-rZk2MsOq@=M( ztR!e&T!_Ezfr43JZX^=nqM^1YWOGiM?v{j)o0z=)TkmCNkTm*Eyn6%u%wnazE{AkJ zUvrdJgNwkYHcuGDA4@Auk1F=Ci5t}Q~VXx#vXNL@J>XQ8qRmK&CgJZLq@^i0`=MFWA z(Zq;EpIRTIy&%wroJEl>Wz}*~=LtTmY6Xn3gjtUA$(_>1GRm@SKt}yX(1U{1jA=1& zT}n>GHI9>OJA-d2HK_yzUy2Q_0%HwdD)^|a7AnQb6;u+ELrqicYSUY8!@9ik&`ZZn zR1T(44@70Kd?Ng3USy4_=tuLI($583L7<2R)v`<#r6W)giCA>VjQGdF5zmSjNT8hr zF=f-S2H?Raq7g@wo7sg;{Yy=jg&XcdBeG&B2c)P^&iw2cZ0>p)z9VPb%;`ee_IqOudi=2=ddz>L9a>9VGs-XBZ|Sq`E{}63L)T z2GyY>#KSym5Q1nJV@3QFG+c)W*xQ0|6L@2&x!+cd2?@!faDh+^Tf1g69BsP&!5uw! zJH8+7=f`HS?NjiBOAIrwv{}AKBtwGY-iDlnM$hY&M@;DIYc4L*Q!_>; zPngZ%C}qy=x0Xv%!$!5VMwdnVtXkSYw=v$b3oO{jiBf5*Fs&mvYAch}&h4a?R6*&m zoH$4y@$VBmWt-Wm5G)7=A`p#t8WK>kRLm!{WmM`uEEy@+!XsX71D9l^w;Sv z>h*NC1j0(_zU}x7M0mO`zRdjb3zJ!$xs(l~^sW?%J9Yh^0~q#nRb_U`SZRz{2Qb*b z_KZwDpQ>d^IkrXp><*m}z8=dqHr^8Zu%1%t^j`GO9(9u&|I_MoP`&3oY6+xiuu9&)s`;!I{$7yTYsD9UywY)GvCB&d z!-n=YCR!LdigF{)Zy~Q5TFO3nr-r=g+}?XjE$)Bd^%I^Vf2)KcrnpwJ7RG2=I1#y% zg=X)!EDkzNYO5x+os`R4{?_$%Hm6Z@>+h~;{e{WIQ)z81BMHbO6uu9B*DE=O(`fObG)szIO-Y`a);`eKEKSCy_FGZ z_e7XoUL%WLnqO;D(124BQx0B9bzHS*xGMmMwg_;ORhk;XTS|Bn5GemVC@QII&IDl7 zSoHgmx}j`FWFNzF-RI|FfuzA z#`h6)v?#SgYs}OEd;H`YOpG$BBaNSe*cp=8tlzGB-NHHnUkLxR`scDRab!mW0*Vg- z0)qRg{_TwHt<23_TI9#Z zMni{{H*#>SNX^9&4IGng5nK9eVTJ?Y`r+f;9P6AyNgL?WzYn!pu>J0O>)bky`S@5G zJ|wL*e1TZ}-X6Rj&mUf2myv!G>&AQ|&`SHNhr8yF0x>+0IkBcrKi>*p4A+k; zrtB5@Q>9Psww!a+&kkF(@yl+2(}x7@!BRXLK>T{R=BN)eJ`vyo&Tc%h*966jY?0VF z)u3m1wh(*qq@H?q>+Xr)X4WCN`(1o}IYbUC~R|$zl|@*cZ0Q6Pk6E{WF-QPR_pbY-Uy3 z^v?fe)p>VnYX@^z63CamsUkp#tM`rIwX~AtbfDi zSC-o<|Gf>48q%kaC`?y<7Vzf(Ox$X(u7>-9W(F>(O7>7#OwNV6y+%~i+t94BSBJt8 zoeG@JUZa{}LX(d}+L#q%@(EEess~oSlox9kKv0dDd7LW!sgfTka-ZYFODC>>NAqLolhed6=pD zCLG+x$mk9?QnW*yJq%bR!A*}wz>%c>#>=q~1doR}c8@wc_pe%!(UVdJQuR8mJKHYh z{X)`mQCbZ0i_|nJehwsWEFIC-Hksob2(xxYzO>Yl6E_17&Tf9Nn-C2xE$o9YI2p)4 z&x8eEGH*i?AcZt>!mgZKQ?PNeIOK9N;|Vl6deS_Vx}+QwQ^v2SxV2r(yxPk`R-STI zS&V!cY)@kM81m-ItjL(@nQr^5G{UuE6vnT_RXv2cR}BY8DHYvl)y1L(X25$TD{bn) zRNbIb?3$NN&!;=TT3$0d;+vFkYzK8l?~PyC z_HGPwZ*k1@@_6I=^7dl1m6^XyDKEYG0_KwWaz!!M;a5QeyRMF|lKPP@N$&I!I5FYm z5I?PUAq-Q*#KHWF_B`QS3qBPI&e{I+V?0$P9L1rdat|2NF=NoQ_}=#qe{+;!iCA9A zvQ!Ouc2afcFQP?vnKaV8m;5zxdh zCM{;R$26-ZgU~coygrGIHy|&!;$L^~@oatHSVobFy-E+OH)DeIbvdH@D-u~5Kc-NY zpYEPZJ}I7?2c)3KyOuw@DrIZw=&GVpWoceVg_oP5+;-_#6_#O$uohnS616}X%C>c+ z%=&VQa?ahPu<>|NI2V=#i{{l#Ngro~Cs;~{C+Tj{sFQQ=R?oTG>;P>#nknt5281)I zB=Rj(>O}D?>E#PX#2ok}8XHFNV72D~3exj1iQa8^cB6$7Y1~pGhO-w}Mt=gB z%cr%E2P%0fzo$Nc*hkQTL(f$u+*DoC`Ho8I0HVmV=$zVj!m<6ws`Jj%n6o^TDil>w zep~GXrv>MCXzneTQQ~9a@?yt2_R-=K!6@@Ov*Pc9uSYAM;wEU(M;DW_53_M5=+B{2 zxETC3j4Rw4=CwM($Hm3v{pDx&(R@LIp(i9%b7fy*=aMyQRu?5k^-Ct&D#JD`b;jKJ zF3>1Avn%VO^jpdq`9%7lXD!z4kiUOrvjbCkb(wRP694q%EL@^vmm>WsR)TvyHv0wj zYu?R1=JvJ*#eO)~ttDXH>B96q=OyQa`wHo0b?iw&;QSxXwh3&{AY!k;R=b11Zn`<0xr;;T)?v(Rr`GleDrC(%!ZYwqip!dDFryuk~|7ovFi^9yi?*k9ViAbc*P z#`u3+0U#@h!z%~=`Y(Sm-XvS~>FGirQ5*>$gUg;ZyE~=`rv5Dk4 z^s;+|`_%I5sGumw`OdSW1ub+I%3c#zZXmGWbu0wn8f+x&-G@ew$`z6vLrI7Oiv@pDcxn*T-TkSra zrw&27>k)8^!?Bhd%468HiP@i)tg7~jKQO_$-!>fgE}=2Y45n9RSogL1B&q^J`&a9h znBsY0NY(%-VJ*pS%_!a-vnUMY0-reL&g#{+hQOk3w3u%rX`_ywOV$N)p)=IUy-{w+ z+Ny#7URFmhwAS1E^~;a@5^>NV3PKZ>-kHgHB=RsQnMp+kht*4C!?(!)-nWxcm(EWy z_oJ%IPqUggIEhLbL%b>Zd1)R35Vp*7c>D7S=@AnS@!gV;a#EwY`IdF)s}5VV!VG*n zAC8NOnr+a-&Z%O66eg%bFm=Uem;3wX#~@JEt~4PyShhseh>m+I`KYN%lrGI#9e&Ad zFssti(44O?dtcsi`d095RKTSsueii1Q^8W*>x^fYuishQdHR*|o=po97V}p1_^53D zQ@vuiN*ejWma_{ZZdiT zi=TigE4<`Xz5pZVk$l17>i0I1f9DL=wE|;_bZpKP=d4JXM49!$2YIh8H!zX$ zkFW-2NMI3oW1d4NGk;)OA${Sd>#)V<-M2^w!#^|%R@c{o@sR?{xm_wS3{3z$%tBZ# z*_#W3ul#Db&*%30++2R2%Se^una}&R35}ZU9>=foVwKWb-A>=b#bTPW>dRPC>Yg32 z9t;`ghF<4D(sL71$kx{qZSTOZ$Ggz`GVP4TM!$WrLwzN!*OITQaAd4(5!7j@8}EW^ z$0cPUoL9r6lLu0Kuel$%>F4QhJiP=U%1sX-9NZdqG8W0gRmN_EP63QRFFAdQ7$7%7 z^Huyb+{qmSY9>}1Sx>{23ocm`TshcP?+#I}7+ zmKb0CZI9|4vr09iA%pXnA1m!7ETF|}FfYDkjWaRcZTvZ%@MfC-W!s_eS*D8r^90;7 zyDRO{ZCGTQJxqQu+srs$o2WbY8>oG11?Q|&|$@5vK0)-}KF z_!ZF|SQv_j1+i{gmK=$fM7bgGm@`B(K&I&7fNjpU0O4;_=x32-!n!`%fH=IjM$^mM zra0GEkoVtt?NVMyGCl9P;>OZxUnlkqDIrRdnM6Egf~2J}4sD~XgA_W4g&PjM${wHU zt{xpaxkU-`q!pC79eNB|fv-7K_CG_g3_)l{+u|C#G`_9qOJ!*ouFjTuYobUl<(^ckPNiefzKVYl$tg*=ExTi9d@{V*l50x{ezSrlLocJ&Q6|+vAjBd;l2;cU7NmvU#PlF-*|qd2n*_p96C710==tXANaygQ0feHG%CmLQ%L zXc>zd3`U%&W}hQ>VNH!$1+QcqIghZa30oh}4sI?hF081Vo#oGtYlGLK!uLB-10ZtF z3qe*yq33l#R`%=vMDar3dCkcBqWAysd|#dcW0}M*j;_D*(an||AHBBGwy=hWXCYZpv!zAd zxTydA?PmBs!s!Y2+$_dX7;mL*qnNP92G7VJQ&%Rn#i8p}*ilSWZCCCTu&os!tkn^H zN?izjjc?n)Cl&9Y7|e+5+-uhmAXw;-*eeCXi_V<k= z-1(T$L|72GhB3HVH6|DnT(~*Y{;e=gXyyxf0b40vb{I)$5H~*(l7ne3r8a>f)I)e! z@TWmCzF{bZB{)k++7@9QOFiCV0!wH*&uk3x0 z!kUAdH`!!QPhHdXkrLpTXbB1}pfZ$ZD{DTa6G>$RzaWO3WCB>~fr^4;pHpV%^Dl;E zT}OS8k9VX|w<%kYg6%vns>urtr68oevNcYiQ-D}2lf5YGicZdFuYCPJ5}w9b)$ENS zUAZE}B6GN%`oX&=Frma%gN1^hHG4LExi!(~)NBDdgKMKzI_wE*C94MHJ<}wbE`A?8 zWM-*fUD&SE3gz*qi4`Uh=}kun@n9eY{$HZ#{->F=XqTpZ1`i{AlvIz|Cnm^sfsL2O zf1$up!Ne(F{@{v1VhTdyP<=`4dxnL~V?f!z;4nzA+Ellz34MDVt<53p+5M2WhQ5q; zb+P&_^Gx`LNa0DVy63#cDPkHzjME{>TpC^?jl_#`Nhjl)hmHwRno%$ypz-NoO)2^}f%Ad4U+k0*PSkL?9BIitvcV1iIyQf}VmY2}xObRg58?ZcAm}PB2kvidx8P z=Q*NlQ?`t42`dU8a2P9VCU#Gi6)$b(bHFA9bjAuModjcBlFP`Vj1Q);9<2Cwn8Cc- z<#M;WM&}G9bfzW_~ zCSk_76edpUgo;7Wk}lZP80MFemKKqI*fN>7#B0t*R1q_$s$CAO?j@^!au3)4p{26$ zXzdVQSgJ_%e7|%54AWpZ8J(ZVPp^e|z;_j-u1jxpt22spuLRxv`{_D+D+(;#CLEbB zh8ROB+igxLDyJ1littgDHxQQ58jM1ZTF?YT)uiKQTRu=i!Xk=?KusxHI1DV*skR(^ zgqu`Dd!wMa=0Rf3fLw^81;!o^zdr*}p?ka%KwylOu_OmeeC;$s zUoBPONO$o(FC;AFD&BF*FD`h?H8$H3w}v`K9@hYOV)v{;pcSXF@Tl(jPb?zm+dP#% zh=uwM02-9z4oEnBWNOfi6CT_N0SDVDFbr=LJS2T8OLhlK7z=8hiE|psd#pp06y)o6Vu5?-{aM4(HR zfYAl6xl5m2a5cJhcFCU^4pvtv@E_*&{BrwfWzPm8;2KOG4rqmykxe(hhQRMxjim)m z%b*+gpFs!cir@&}Xd@zEEy4;(hkJCy7$R&qpq~UN;|QI*&*nFsYwpf@K8oAQdB$g# zw<&@O>J76H>_@2*kjb4f^n8XORxgon`bTcd>n*)O$i?On1b&H`bEliUE1p0f;|dL@ zHhHVg@oG@UKf%@}TYHX~+Zq!{OBc|=`G)pQULY+l&O#i`V{z4qi#ASc))fA2$(rUfOIy#3uad_?{9SyS9LpaA#V1qPm3aM5vSXfCK zF2_-uX!yq$G#0Q6UYg8C;Op5rJUKEGK4FPfE;d287czA@Nr^rk<=lS< zYx$ZejlL6JE~Ah8(h=;vVPCW;W#{y0xK`^-16P6i#8Kw~2p);A5OO#x_tV+)bLi2oQPB~gem4_9*DgaHdS(Rmz(DK>H=OiXC;b+cB# z5BYEAo8e%y?F}qcfB^mL(gY>~7fXaRpfpHUv%kEjMuXD(cX|oPZ}yk+D!iTkXsEcq zMg)vS)Hd``hC8;-ts?>vcuI?Wj0*w1?sPtc(M_|{(GH3fwGSAD*s!t3|7cW*86iE` z(&6?EF(#cR-S%-XEnpzq`gt0VR4@9mh%CU<@++!szejiCzrht{fI(1!{{O18AC~q1 zYX4h<_TORut;_m9F=)X550%z`hyORR`+vewepFungXUF~0f+d{GU!jQ|FN#*|MC78 D;h<$3 literal 0 HcmV?d00001 diff --git a/plugin-packages/dashscope-qwen-image/README.md b/plugin-packages/dashscope-qwen-image/README.md new file mode 100644 index 000000000..8a90ec8be --- /dev/null +++ b/plugin-packages/dashscope-qwen-image/README.md @@ -0,0 +1,5 @@ +# DashScope Qwen / Wan Image + +该目录是独立官方协议插件源码。后端从生成的 `dashscope-qwen-image.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。 + +完整接口见 [docs/interface.md](docs/interface.md)。 diff --git a/plugin-packages/dashscope-qwen-image/docs/interface.md b/plugin-packages/dashscope-qwen-image/docs/interface.md new file mode 100644 index 000000000..7e2d52e1f --- /dev/null +++ b/plugin-packages/dashscope-qwen-image/docs/interface.md @@ -0,0 +1,518 @@ +# DashScope Qwen / Wan Image 接口字段 + +## 协议身份 + +- 插件 ID:`dashscope-qwen-image`。 +- Provider ID:`dashscope-qwen-image`。 +- 能力:`image`。 +- 默认 Base URL:`https://dashscope.aliyuncs.com`。 +- 鉴权驱动:`bearer`。 +- 创建:`POST /api/v1/services/aigc/image-generation/generation`。 +- 查询:`GET /api/v1/tasks/{{taskId}}`。 + +## 配置字段 + +| 字段 | 类型 | 必填 | 含义 | +| --- | --- | --- | --- | +| `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/v1/services/aigc/image-generation/generation"` | +| `create.contentType` | `"application/json"` | +| `create.body.model` | `{"$ref":"request.model"}` | +| `create.body.input.messages[0].role` | `"user"` | +| `create.body.input.messages[0].content` | `{"$concatArrays":[{"$map":{"from":{"$sortByOrder":{"$ref":"request.images"}},"as":"media","in":{"image":{"$coalesce":[{"$ref":"media.dataUrl"},{"$ref":"media.url"}]}}}},[{"text":{"$ref":"request.prompt"}}]]}` | +| `create.body.parameters.size` | `{"$omitEmpty":{"$switch":{"cases":[{"when":{"$eq":[{"$ref":"request.aspectRatio"},"1:1"]},"then":"1024*1024"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"3:4"]},"then":"960*1280"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"4:3"]},"then":"1280*960"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"2:3"]},"then":"1024*1536"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"3:2"]},"then":"1536*1024"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"9:16"]},"then":"864*1536"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"16:9"]},"then":"1536*864"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"1024x1024"]},"then":"1024*1024"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"1024x1536"]},"then":"1024*1536"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"1536x1024"]},"then":"1536*1024"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"1024x1280"]},"then":"1024*1280"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"1280x1024"]},"then":"1280*1024"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"960x1280"]},"then":"960*1280"},{"when":{"$eq":[{"$ref":"request.aspectRatio"},"1280x960"]},"then":"1280*960"}],"default":null}}}` | +| `create.body.parameters.n` | `{"$if":{"condition":{"$gt":[{"$toInt":{"$ref":"request.imageCount"}},0]},"then":{"$toInt":{"$ref":"request.imageCount"}},"else":1}}` | +| `create.body.parameters.prompt_extend` | `true` | +| `create.body.parameters.enable_thinking` | `false` | +| `create.body.parameters.negative_prompt` | `{"$omitEmpty":{"$ref":"request.providerOptions.dashscope-qwen-image.negative_prompt"}}` | +| `create.body.parameters.seed` | `{"$omitEmpty":{"$ref":"request.providerOptions.dashscope-qwen-image.seed"}}` | +| `create.body.parameters.watermark` | `{"$omitEmpty":{"$ref":"request.watermark"}}` | +| `create.headers.X-DashScope-Async` | `"enable"` | +| `poll.method` | `"GET"` | +| `poll.path` | `"/api/v1/tasks/{{taskId}}"` | +| `poll.contentType` | `"application/json"` | + +## Provider 扩展键 + +- `providerOptions.dashscope-qwen-image.negative_prompt` +- `providerOptions.dashscope-qwen-image.seed` + +动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。 + +## 响应映射逐字段清单 + +| 映射位置 | 上游路径或转换表达式 | +| --- | --- | +| `response.taskId` | `{"$coalesce":[{"$ref":"response.output.task_id"},{"$ref":"response.task_id"},{"$ref":"taskId"}]}` | +| `response.status` | `{"$coalesce":[{"$ref":"response.output.task_status"},{"$ref":"response.status"},"pending"]}` | +| `response.message` | `{"$coalesce":[{"$ref":"response.output.message"},{"$ref":"response.message"}]}` | +| `response.images` | `{"$map":{"from":{"$ref":"response.output.choices.0.message.content"},"as":"item","in":{"url":{"$omitEmpty":{"$ref":"item.image"}}}}}` | +| `response.errorPaths[0]` | `"code"` | +| `response.errorPaths[1]` | `"output.code"` | +| `response.resultEphemeral` | `true` | +| `response.messagePaths[0]` | `"message"` | +| `response.messagePaths[1]` | `"output.message"` | + +## 响应与错误 + +插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。 + +## 兼容边界 + +百炼多模态图像端点(image-generation 异步任务)。文生图与图生图共用同一入口:不传 image 为文生图,传 1-3 张 image 为图生图。参考图通过 input.messages[].content[].image 传输(优先 Base64 data URL),不使用 OpenAI 的 /v1/images/edits multipart。size 采用“宽*高”,未登记的档位省略并由模型自动推荐;enable_thinking 固定为 false,因为官方要求非流式调用关闭思考模式。 + + +## Manifest 完整接口定义 + +以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。 + +```json +{ + "apiVersion": "yingce.plugin/v2", + "id": "dashscope-qwen-image", + "name": "DashScope Qwen / Wan Image", + "version": "2.0.0", + "author": "Alibaba Cloud / 影策", + "description": "DashScope Qwen / Wan Image 独立请求协议插件。", + "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": "dashscope-qwen-image", + "label": "DashScope Qwen / Wan Image", + "capabilities": [ + "image" + ], + "scopes": [ + "admin.system-channel", + "user.custom-channel", + "canvas", + "creation", + "agent" + ], + "baseUrl": "https://dashscope.aliyuncs.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/v1/services/aigc/image-generation/generation", + "contentType": "application/json", + "body": { + "model": { + "$ref": "request.model" + }, + "input": { + "messages": [ + { + "role": "user", + "content": { + "$concatArrays": [ + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.images" + } + }, + "as": "media", + "in": { + "image": { + "$coalesce": [ + { + "$ref": "media.dataUrl" + }, + { + "$ref": "media.url" + } + ] + } + } + } + }, + [ + { + "text": { + "$ref": "request.prompt" + } + } + ] + ] + } + } + ] + }, + "parameters": { + "size": { + "$omitEmpty": { + "$switch": { + "cases": [ + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1:1" + ] + }, + "then": "1024*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "3:4" + ] + }, + "then": "960*1280" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "4:3" + ] + }, + "then": "1280*960" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "2:3" + ] + }, + "then": "1024*1536" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "3:2" + ] + }, + "then": "1536*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "9:16" + ] + }, + "then": "864*1536" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "16:9" + ] + }, + "then": "1536*864" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1024x1024" + ] + }, + "then": "1024*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1024x1536" + ] + }, + "then": "1024*1536" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1536x1024" + ] + }, + "then": "1536*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1024x1280" + ] + }, + "then": "1024*1280" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1280x1024" + ] + }, + "then": "1280*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "960x1280" + ] + }, + "then": "960*1280" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1280x960" + ] + }, + "then": "1280*960" + } + ], + "default": null + } + } + }, + "n": { + "$if": { + "condition": { + "$gt": [ + { + "$toInt": { + "$ref": "request.imageCount" + } + }, + 0 + ] + }, + "then": { + "$toInt": { + "$ref": "request.imageCount" + } + }, + "else": 1 + } + }, + "prompt_extend": true, + "enable_thinking": false, + "negative_prompt": { + "$omitEmpty": { + "$ref": "request.providerOptions.dashscope-qwen-image.negative_prompt" + } + }, + "seed": { + "$omitEmpty": { + "$ref": "request.providerOptions.dashscope-qwen-image.seed" + } + }, + "watermark": { + "$omitEmpty": { + "$ref": "request.watermark" + } + } + } + }, + "headers": { + "X-DashScope-Async": "enable" + }, + "originPath": true + }, + "poll": { + "method": "GET", + "path": "/api/v1/tasks/{{taskId}}", + "originPath": true + }, + "response": { + "taskId": { + "$coalesce": [ + { + "$ref": "response.output.task_id" + }, + { + "$ref": "response.task_id" + }, + { + "$ref": "taskId" + } + ] + }, + "status": { + "$coalesce": [ + { + "$ref": "response.output.task_status" + }, + { + "$ref": "response.status" + }, + "pending" + ] + }, + "message": { + "$coalesce": [ + { + "$ref": "response.output.message" + }, + { + "$ref": "response.message" + } + ] + }, + "images": { + "$map": { + "from": { + "$ref": "response.output.choices.0.message.content" + }, + "as": "item", + "in": { + "url": { + "$omitEmpty": { + "$ref": "item.image" + } + } + } + } + }, + "errorPaths": [ + "code", + "output.code" + ], + "resultEphemeral": true, + "messagePaths": [ + "message", + "output.message" + ] + } + } + ] + } +} +``` + diff --git a/plugin-packages/dashscope-qwen-image/manifest.json b/plugin-packages/dashscope-qwen-image/manifest.json new file mode 100644 index 000000000..9b9b7bb4b --- /dev/null +++ b/plugin-packages/dashscope-qwen-image/manifest.json @@ -0,0 +1,426 @@ +{ + "apiVersion": "yingce.plugin/v2", + "id": "dashscope-qwen-image", + "name": "DashScope Qwen / Wan Image", + "version": "2.0.0", + "author": "Alibaba Cloud / 影策", + "description": "DashScope Qwen / Wan Image 独立请求协议插件。", + "documentation": "# DashScope Qwen / Wan Image\n\n该目录是独立官方协议插件源码。后端从生成的 `dashscope-qwen-image.yingce-plugin` 包加载,不依赖系统内置 `host:` 适配器。\n\n完整接口见 [docs/interface.md](docs/interface.md)。\n\n---\n\n# DashScope Qwen / Wan Image 接口字段\n\n## 协议身份\n\n- 插件 ID:`dashscope-qwen-image`。\n- Provider ID:`dashscope-qwen-image`。\n- 能力:`image`。\n- 默认 Base URL:`https://dashscope.aliyuncs.com`。\n- 鉴权驱动:`bearer`。\n- 创建:`POST /api/v1/services/aigc/image-generation/generation`。\n- 查询:`GET /api/v1/tasks/{{taskId}}`。\n\n## 配置字段\n\n| 字段 | 类型 | 必填 | 含义 |\n| --- | --- | --- | --- |\n| `apiKey` | secret | 是 | API Key |\n\n## 统一字段映射\n\n| 统一字段 | 类型 | 必填 | 上游映射 | 说明 |\n| --- | --- | --- | --- | --- |\n| `model` | string | 是 | `model` | 图片模型 ID。 |\n| `prompt` | string | 是 | `prompt` | 图片提示词。 |\n| `images` | media[] | 否 | `provider image/reference fields` | 参考图或编辑源图,role 由业务层确定。 |\n| `imageCount` | integer | 否 | `n/sample_count` | 输出数量。 |\n| `aspectRatio` | string | 否 | `size/aspect_ratio` | 比例或尺寸,语义按协议说明。 |\n| `resolution` | string | 否 | `resolution/imageSize` | 分辨率档位。 |\n| `quality` | string | 否 | `quality` | 质量档位。 |\n| `providerOptions` | object | 否 | `provider-specific fields` | 插件命名空间内的厂商扩展字段。 |\n\n## 上游请求模板逐字段清单\n\n下表由插件请求模板生成,覆盖 body、query、headers 和 multipart 文件声明中的每个字段。\n\n| 上游位置 | 值或转换表达式 |\n| --- | --- |\n| `create.method` | `\"POST\"` |\n| `create.path` | `\"/api/v1/services/aigc/image-generation/generation\"` |\n| `create.contentType` | `\"application/json\"` |\n| `create.body.model` | `{\"$ref\":\"request.model\"}` |\n| `create.body.input.messages[0].role` | `\"user\"` |\n| `create.body.input.messages[0].content` | `{\"$concatArrays\":[{\"$map\":{\"from\":{\"$sortByOrder\":{\"$ref\":\"request.images\"}},\"as\":\"media\",\"in\":{\"image\":{\"$coalesce\":[{\"$ref\":\"media.dataUrl\"},{\"$ref\":\"media.url\"}]}}}},[{\"text\":{\"$ref\":\"request.prompt\"}}]]}` |\n| `create.body.parameters.size` | `{\"$omitEmpty\":{\"$switch\":{\"cases\":[{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1:1\"]},\"then\":\"1024*1024\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"3:4\"]},\"then\":\"960*1280\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"4:3\"]},\"then\":\"1280*960\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"2:3\"]},\"then\":\"1024*1536\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"3:2\"]},\"then\":\"1536*1024\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"9:16\"]},\"then\":\"864*1536\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"16:9\"]},\"then\":\"1536*864\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1024x1024\"]},\"then\":\"1024*1024\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1024x1536\"]},\"then\":\"1024*1536\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1536x1024\"]},\"then\":\"1536*1024\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1024x1280\"]},\"then\":\"1024*1280\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1280x1024\"]},\"then\":\"1280*1024\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"960x1280\"]},\"then\":\"960*1280\"},{\"when\":{\"$eq\":[{\"$ref\":\"request.aspectRatio\"},\"1280x960\"]},\"then\":\"1280*960\"}],\"default\":null}}}` |\n| `create.body.parameters.n` | `{\"$if\":{\"condition\":{\"$gt\":[{\"$toInt\":{\"$ref\":\"request.imageCount\"}},0]},\"then\":{\"$toInt\":{\"$ref\":\"request.imageCount\"}},\"else\":1}}` |\n| `create.body.parameters.prompt_extend` | `true` |\n| `create.body.parameters.enable_thinking` | `false` |\n| `create.body.parameters.negative_prompt` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.dashscope-qwen-image.negative_prompt\"}}` |\n| `create.body.parameters.seed` | `{\"$omitEmpty\":{\"$ref\":\"request.providerOptions.dashscope-qwen-image.seed\"}}` |\n| `create.body.parameters.watermark` | `{\"$omitEmpty\":{\"$ref\":\"request.watermark\"}}` |\n| `create.headers.X-DashScope-Async` | `\"enable\"` |\n| `poll.method` | `\"GET\"` |\n| `poll.path` | `\"/api/v1/tasks/{{taskId}}\"` |\n| `poll.contentType` | `\"application/json\"` |\n\n## Provider 扩展键\n\n- `providerOptions.dashscope-qwen-image.negative_prompt`\n- `providerOptions.dashscope-qwen-image.seed`\n\n动态模型或工作流允许使用文档声明的完整 `parameters/input/extra_body` 对象;该对象是协议本身的开放 schema,不会被宿主裁剪。\n\n## 响应映射逐字段清单\n\n| 映射位置 | 上游路径或转换表达式 |\n| --- | --- |\n| `response.taskId` | `{\"$coalesce\":[{\"$ref\":\"response.output.task_id\"},{\"$ref\":\"response.task_id\"},{\"$ref\":\"taskId\"}]}` |\n| `response.status` | `{\"$coalesce\":[{\"$ref\":\"response.output.task_status\"},{\"$ref\":\"response.status\"},\"pending\"]}` |\n| `response.message` | `{\"$coalesce\":[{\"$ref\":\"response.output.message\"},{\"$ref\":\"response.message\"}]}` |\n| `response.images` | `{\"$map\":{\"from\":{\"$ref\":\"response.output.choices.0.message.content\"},\"as\":\"item\",\"in\":{\"url\":{\"$omitEmpty\":{\"$ref\":\"item.image\"}}}}}` |\n| `response.errorPaths[0]` | `\"code\"` |\n| `response.errorPaths[1]` | `\"output.code\"` |\n| `response.resultEphemeral` | `true` |\n| `response.messagePaths[0]` | `\"message\"` |\n| `response.messagePaths[1]` | `\"output.message\"` |\n\n## 响应与错误\n\n插件把上游 task/status/text/media/usage 映射为统一结果。临时媒体 URL 标记为 ephemeral,由宿主立即下载持久化。HTTP 错误、业务 code 和 error object 保持失败语义,不包装成成功。\n\n## 兼容边界\n\n百炼多模态图像端点(image-generation 异步任务)。文生图与图生图共用同一入口:不传 image 为文生图,传 1-3 张 image 为图生图。参考图通过 input.messages[].content[].image 传输(优先 Base64 data URL),不使用 OpenAI 的 /v1/images/edits multipart。size 采用“宽*高”,未登记的档位省略并由模型自动推荐;enable_thinking 固定为 false,因为官方要求非流式调用关闭思考模式。\n\n\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v2\",\n \"id\": \"dashscope-qwen-image\",\n \"name\": \"DashScope Qwen / Wan Image\",\n \"version\": \"2.0.0\",\n \"author\": \"Alibaba Cloud / 影策\",\n \"description\": \"DashScope Qwen / Wan Image 独立请求协议插件。\",\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\",\n \"permissions\": [\n \"generation.run\",\n \"media.read\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"apiKey\",\n \"type\": \"secret\",\n \"label\": \"API Key\",\n \"required\": true\n }\n ]\n },\n \"contributes\": {\n \"providers\": [\n {\n \"id\": \"dashscope-qwen-image\",\n \"label\": \"DashScope Qwen / Wan Image\",\n \"capabilities\": [\n \"image\"\n ],\n \"scopes\": [\n \"admin.system-channel\",\n \"user.custom-channel\",\n \"canvas\",\n \"creation\",\n \"agent\"\n ],\n \"baseUrl\": \"https://dashscope.aliyuncs.com\",\n \"requiresPublicMediaUrls\": false,\n \"auth\": {\n \"type\": \"bearer\",\n \"field\": \"apiKey\"\n },\n \"parameters\": [\n {\n \"name\": \"model\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"model\",\n \"description\": \"图片模型 ID。\"\n },\n {\n \"name\": \"prompt\",\n \"type\": \"string\",\n \"required\": true,\n \"mapping\": \"prompt\",\n \"description\": \"图片提示词。\"\n },\n {\n \"name\": \"images\",\n \"type\": \"media[]\",\n \"required\": false,\n \"mapping\": \"provider image/reference fields\",\n \"description\": \"参考图或编辑源图,role 由业务层确定。\"\n },\n {\n \"name\": \"imageCount\",\n \"type\": \"integer\",\n \"required\": false,\n \"mapping\": \"n/sample_count\",\n \"description\": \"输出数量。\"\n },\n {\n \"name\": \"aspectRatio\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"size/aspect_ratio\",\n \"description\": \"比例或尺寸,语义按协议说明。\"\n },\n {\n \"name\": \"resolution\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"resolution/imageSize\",\n \"description\": \"分辨率档位。\"\n },\n {\n \"name\": \"quality\",\n \"type\": \"string\",\n \"required\": false,\n \"mapping\": \"quality\",\n \"description\": \"质量档位。\"\n },\n {\n \"name\": \"providerOptions\",\n \"type\": \"object\",\n \"required\": false,\n \"mapping\": \"provider-specific fields\",\n \"description\": \"插件命名空间内的厂商扩展字段。\"\n }\n ],\n \"create\": {\n \"method\": \"POST\",\n \"path\": \"/api/v1/services/aigc/image-generation/generation\",\n \"contentType\": \"application/json\",\n \"body\": {\n \"model\": {\n \"$ref\": \"request.model\"\n },\n \"input\": {\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": {\n \"$concatArrays\": [\n {\n \"$map\": {\n \"from\": {\n \"$sortByOrder\": {\n \"$ref\": \"request.images\"\n }\n },\n \"as\": \"media\",\n \"in\": {\n \"image\": {\n \"$coalesce\": [\n {\n \"$ref\": \"media.dataUrl\"\n },\n {\n \"$ref\": \"media.url\"\n }\n ]\n }\n }\n }\n },\n [\n {\n \"text\": {\n \"$ref\": \"request.prompt\"\n }\n }\n ]\n ]\n }\n }\n ]\n },\n \"parameters\": {\n \"size\": {\n \"$omitEmpty\": {\n \"$switch\": {\n \"cases\": [\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1:1\"\n ]\n },\n \"then\": \"1024*1024\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"3:4\"\n ]\n },\n \"then\": \"960*1280\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"4:3\"\n ]\n },\n \"then\": \"1280*960\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"2:3\"\n ]\n },\n \"then\": \"1024*1536\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"3:2\"\n ]\n },\n \"then\": \"1536*1024\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"9:16\"\n ]\n },\n \"then\": \"864*1536\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"16:9\"\n ]\n },\n \"then\": \"1536*864\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1024x1024\"\n ]\n },\n \"then\": \"1024*1024\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1024x1536\"\n ]\n },\n \"then\": \"1024*1536\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1536x1024\"\n ]\n },\n \"then\": \"1536*1024\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1024x1280\"\n ]\n },\n \"then\": \"1024*1280\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1280x1024\"\n ]\n },\n \"then\": \"1280*1024\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"960x1280\"\n ]\n },\n \"then\": \"960*1280\"\n },\n {\n \"when\": {\n \"$eq\": [\n {\n \"$ref\": \"request.aspectRatio\"\n },\n \"1280x960\"\n ]\n },\n \"then\": \"1280*960\"\n }\n ],\n \"default\": null\n }\n }\n },\n \"n\": {\n \"$if\": {\n \"condition\": {\n \"$gt\": [\n {\n \"$toInt\": {\n \"$ref\": \"request.imageCount\"\n }\n },\n 0\n ]\n },\n \"then\": {\n \"$toInt\": {\n \"$ref\": \"request.imageCount\"\n }\n },\n \"else\": 1\n }\n },\n \"prompt_extend\": true,\n \"enable_thinking\": false,\n \"negative_prompt\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.dashscope-qwen-image.negative_prompt\"\n }\n },\n \"seed\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.providerOptions.dashscope-qwen-image.seed\"\n }\n },\n \"watermark\": {\n \"$omitEmpty\": {\n \"$ref\": \"request.watermark\"\n }\n }\n }\n },\n \"headers\": {\n \"X-DashScope-Async\": \"enable\"\n },\n \"originPath\": true\n },\n \"poll\": {\n \"method\": \"GET\",\n \"path\": \"/api/v1/tasks/{{taskId}}\",\n \"originPath\": true\n },\n \"response\": {\n \"taskId\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.output.task_id\"\n },\n {\n \"$ref\": \"response.task_id\"\n },\n {\n \"$ref\": \"taskId\"\n }\n ]\n },\n \"status\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.output.task_status\"\n },\n {\n \"$ref\": \"response.status\"\n },\n \"pending\"\n ]\n },\n \"message\": {\n \"$coalesce\": [\n {\n \"$ref\": \"response.output.message\"\n },\n {\n \"$ref\": \"response.message\"\n }\n ]\n },\n \"images\": {\n \"$map\": {\n \"from\": {\n \"$ref\": \"response.output.choices.0.message.content\"\n },\n \"as\": \"item\",\n \"in\": {\n \"url\": {\n \"$omitEmpty\": {\n \"$ref\": \"item.image\"\n }\n }\n }\n }\n },\n \"errorPaths\": [\n \"code\",\n \"output.code\"\n ],\n \"resultEphemeral\": true,\n \"messagePaths\": [\n \"message\",\n \"output.message\"\n ]\n }\n }\n ]\n }\n}\n```\n\n", + "permissions": [ + "generation.run", + "media.read" + ], + "configuration": { + "fields": [ + { + "name": "apiKey", + "type": "secret", + "label": "API Key", + "required": true + } + ] + }, + "contributes": { + "providers": [ + { + "id": "dashscope-qwen-image", + "label": "DashScope Qwen / Wan Image", + "capabilities": [ + "image" + ], + "scopes": [ + "admin.system-channel", + "user.custom-channel", + "canvas", + "creation", + "agent" + ], + "baseUrl": "https://dashscope.aliyuncs.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/v1/services/aigc/image-generation/generation", + "contentType": "application/json", + "body": { + "model": { + "$ref": "request.model" + }, + "input": { + "messages": [ + { + "role": "user", + "content": { + "$concatArrays": [ + { + "$map": { + "from": { + "$sortByOrder": { + "$ref": "request.images" + } + }, + "as": "media", + "in": { + "image": { + "$coalesce": [ + { + "$ref": "media.dataUrl" + }, + { + "$ref": "media.url" + } + ] + } + } + } + }, + [ + { + "text": { + "$ref": "request.prompt" + } + } + ] + ] + } + } + ] + }, + "parameters": { + "size": { + "$omitEmpty": { + "$switch": { + "cases": [ + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1:1" + ] + }, + "then": "1024*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "3:4" + ] + }, + "then": "960*1280" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "4:3" + ] + }, + "then": "1280*960" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "2:3" + ] + }, + "then": "1024*1536" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "3:2" + ] + }, + "then": "1536*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "9:16" + ] + }, + "then": "864*1536" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "16:9" + ] + }, + "then": "1536*864" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1024x1024" + ] + }, + "then": "1024*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1024x1536" + ] + }, + "then": "1024*1536" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1536x1024" + ] + }, + "then": "1536*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1024x1280" + ] + }, + "then": "1024*1280" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1280x1024" + ] + }, + "then": "1280*1024" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "960x1280" + ] + }, + "then": "960*1280" + }, + { + "when": { + "$eq": [ + { + "$ref": "request.aspectRatio" + }, + "1280x960" + ] + }, + "then": "1280*960" + } + ], + "default": null + } + } + }, + "n": { + "$if": { + "condition": { + "$gt": [ + { + "$toInt": { + "$ref": "request.imageCount" + } + }, + 0 + ] + }, + "then": { + "$toInt": { + "$ref": "request.imageCount" + } + }, + "else": 1 + } + }, + "prompt_extend": true, + "enable_thinking": false, + "negative_prompt": { + "$omitEmpty": { + "$ref": "request.providerOptions.dashscope-qwen-image.negative_prompt" + } + }, + "seed": { + "$omitEmpty": { + "$ref": "request.providerOptions.dashscope-qwen-image.seed" + } + }, + "watermark": { + "$omitEmpty": { + "$ref": "request.watermark" + } + } + } + }, + "headers": { + "X-DashScope-Async": "enable" + }, + "originPath": true + }, + "poll": { + "method": "GET", + "path": "/api/v1/tasks/{{taskId}}", + "originPath": true + }, + "response": { + "taskId": { + "$coalesce": [ + { + "$ref": "response.output.task_id" + }, + { + "$ref": "response.task_id" + }, + { + "$ref": "taskId" + } + ] + }, + "status": { + "$coalesce": [ + { + "$ref": "response.output.task_status" + }, + { + "$ref": "response.status" + }, + "pending" + ] + }, + "message": { + "$coalesce": [ + { + "$ref": "response.output.message" + }, + { + "$ref": "response.message" + } + ] + }, + "images": { + "$map": { + "from": { + "$ref": "response.output.choices.0.message.content" + }, + "as": "item", + "in": { + "url": { + "$omitEmpty": { + "$ref": "item.image" + } + } + } + } + }, + "errorPaths": [ + "code", + "output.code" + ], + "resultEphemeral": true, + "messagePaths": [ + "message", + "output.message" + ] + } + } + ] + } +} diff --git a/plugin-packages/generate-catalog.mjs b/plugin-packages/generate-catalog.mjs index d73bb5e81..af16aae4e 100644 --- a/plugin-packages/generate-catalog.mjs +++ b/plugin-packages/generate-catalog.mjs @@ -823,6 +823,54 @@ add({ }) }); +// Qwen-Image 3.0 / Wan 2.7 图像属于百炼多模态模型,必须走多模态端点: +// 用 OpenAI 的 /v1/images/generations 或 /v1/images/edits 会被网关判为“模型与端点不匹配”, +// 官方错误码把它表现为 url error;参考图只能放在 input.messages[].content[].image。 +// size 使用 DashScope 的 “宽*高”(星号),未登记的档位直接省略,由模型按提示词自动推荐分辨率。 +const dashscopeMultimodalImageSizes = [ + ["1:1", "1024*1024"], ["3:4", "960*1280"], ["4:3", "1280*960"], ["2:3", "1024*1536"], ["3:2", "1536*1024"], + ["9:16", "864*1536"], ["16:9", "1536*864"], + ["1024x1024", "1024*1024"], ["1024x1536", "1024*1536"], ["1536x1024", "1536*1024"], + ["1024x1280", "1024*1280"], ["1280x1024", "1280*1024"], ["960x1280", "960*1280"], ["1280x960", "1280*960"] +]; + +add({ + id: "dashscope-qwen-image", providerId: "dashscope-qwen-image", name: "DashScope Qwen / Wan Image", vendor: "Alibaba Cloud", capability: "image", + baseUrl: "https://dashscope.aliyuncs.com", auth: bearer, params: imageParams, requiresPublicMediaUrls: false, + notes: "百炼多模态图像端点(image-generation 异步任务)。文生图与图生图共用同一入口:不传 image 为文生图,传 1-3 张 image 为图生图。参考图通过 input.messages[].content[].image 传输(优先 Base64 data URL),不使用 OpenAI 的 /v1/images/edits multipart。size 采用“宽*高”,未登记的档位省略并由模型自动推荐;enable_thinking 固定为 false,因为官方要求非流式调用关闭思考模式。", + create: jsonCreate("/api/v1/services/aigc/image-generation/generation", { + model: ref("request.model"), + input: { + messages: [{ + role: "user", + content: { + $concatArrays: [ + map(sorted(ref("request.images")), "media", { image: coalesce(ref("media.dataUrl"), ref("media.url")) }), + [{ text: ref("request.prompt") }] + ] + } + }] + }, + parameters: { + size: omit({ $switch: { cases: dashscopeMultimodalImageSizes.map(([ratio, size]) => ({ when: eq(ref("request.aspectRatio"), ratio), then: size })), default: null } }), + n: conditional(gt({ $toInt: ref("request.imageCount") }, 0), { $toInt: ref("request.imageCount") }, 1), + prompt_extend: true, + enable_thinking: false, + negative_prompt: omit(ref("request.providerOptions.dashscope-qwen-image.negative_prompt")), + seed: omit(ref("request.providerOptions.dashscope-qwen-image.seed")), + watermark: omit(ref("request.watermark")) + } + }, { headers: { "X-DashScope-Async": "enable" }, originPath: true }), + poll: { method: "GET", path: "/api/v1/tasks/{{taskId}}", originPath: true }, + response: asyncResponse("image", { + taskId: coalesce(ref("response.output.task_id"), ref("response.task_id"), ref("taskId")), + status: coalesce(ref("response.output.task_status"), ref("response.status"), "pending"), + message: coalesce(ref("response.output.message"), ref("response.message")), + images: map(ref("response.output.choices.0.message.content"), "item", { url: omit(ref("item.image")) }), + errorPaths: ["code", "output.code"], messagePaths: ["message", "output.message"] + }) +}); + for (const [id, name, vendor, capability, baseUrl, createPath, pollPath, requestShape] of [ ["dashscope-qwen-native", "DashScope Qwen Native", "Alibaba Cloud", "text", "https://dashscope.aliyuncs.com", "/api/v1/services/aigc/text-generation/generation", null, "input+parameters"], ["minimax-text-native", "MiniMax Text Native", "MiniMax", "text", "https://api.minimax.chat", "/v1/text/chatcompletion_v2", null, "messages+tokens_to_generate"],