feat(image): integrate PoloX image editing workflows
Add WaveSpeed image editing and image tools plugins, canvas image editing workflows, layer selection, and Cloud Agent image tools with approval-safe transient references.
This commit is contained in:
53
backend/internal/protocol/image_tools_test.go
Normal file
53
backend/internal/protocol/image_tools_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestImageToolsProvidersBuildAsyncImageRequests(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
provider string
|
||||
model string
|
||||
path string
|
||||
}{
|
||||
{name: "remove background", provider: "image-tools-remove-background", model: "bria/remove-background", path: "/bria/remove-background"},
|
||||
{name: "layer decomposition", provider: "image-tools-layer-decomposition", model: "bytedance/seedream-v5.0-pro/layer-decomposition", path: "/bytedance/seedream-v5.0-pro/layer-decomposition"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
adapter := officialPackageAdapter(t, "image-tools.yingce-plugin", tc.provider)
|
||||
if !adapter.Metadata().RequiresPublicMediaURLs {
|
||||
t.Fatal("image tools must receive hydrated public image URLs")
|
||||
}
|
||||
created, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{
|
||||
Model: tc.model,
|
||||
Prompt: "preserve the source image while applying the requested image operation",
|
||||
Images: []MediaReference{{URL: "https://cdn.example/source.png", Role: "edit_source", Order: 0}},
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if created.Method != "POST" || created.Path != tc.path || created.ContentType != "application/json" {
|
||||
t.Fatalf("create request = %#v", created)
|
||||
}
|
||||
body := manifestTestBody(t, created)
|
||||
if body["image"] != "https://cdn.example/source.png" {
|
||||
t.Fatalf("source image mapping = %#v", body["image"])
|
||||
}
|
||||
result, err := adapter.ParseCreate(context.Background(), []byte(`{"data":{"id":"task-image-tool-1","status":"processing"}}`))
|
||||
if err != nil || result.TaskID != "task-image-tool-1" || result.Status != StatusProcessing {
|
||||
t.Fatalf("create result = %#v, err = %v", result, err)
|
||||
}
|
||||
poll, err := adapter.BuildPoll(context.Background(), PollContext{TaskID: result.TaskID})
|
||||
if err != nil || poll.Method != "GET" || poll.Path != "/predictions/task-image-tool-1/result" {
|
||||
t.Fatalf("poll request = %#v, err = %v", poll, err)
|
||||
}
|
||||
completed, err := adapter.ParsePoll(context.Background(), PollContext{TaskID: result.TaskID}, []byte(`{"data":{"id":"task-image-tool-1","status":"completed","outputs":["https://cdn.example/output.png"]}}`))
|
||||
if err != nil || completed.Status != StatusSucceeded || completed.Result == nil || len(completed.Result.Images) != 1 || completed.Result.Images[0].URL != "https://cdn.example/output.png" {
|
||||
t.Fatalf("poll result = %#v, err = %v", completed, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -848,7 +848,11 @@ func buildManifestOperation(operation ManifestOperation, auth ManifestAuth, requ
|
||||
return RequestSpec{}, fmt.Errorf("evaluate request path: %w", err)
|
||||
}
|
||||
path := strings.ReplaceAll(manifestString(evaluatedPath), "{{taskId}}", url.PathEscape(taskID))
|
||||
path = strings.ReplaceAll(path, "{{model}}", url.PathEscape(request.Model))
|
||||
// Model identifiers from async aggregators commonly contain path segments
|
||||
// (for example openai/gpt-image/edit). Escape each segment while preserving
|
||||
// the provider's intentional slash separators in the manifest path.
|
||||
escapedModel := strings.ReplaceAll(url.PathEscape(request.Model), "%2F", "/")
|
||||
path = strings.ReplaceAll(path, "{{model}}", escapedModel)
|
||||
path = interpolateManifestString(path, env)
|
||||
if !isRelativePath(path) {
|
||||
return RequestSpec{}, fmt.Errorf("evaluated request path must be relative: %q", path)
|
||||
|
||||
Reference in New Issue
Block a user