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:
Codex committed 2026-09-18 20:34:28 +08:00
1 parent c7c37f2d4f
commit 6abaa7b45a
34 files changed
+2537 -75

No files matched your search

@@ -140,6 +140,80 @@ func TestCloudAgentToolsFollowCanvasCapabilityRegistry(t *testing.T) {
}
}
func TestCloudAgentImageEditingToolsRespectPermissionBoundaries(t *testing.T) {
readOnly := agentTestRequest()
readOnly.PermissionMode = "read_only"
readOnly.ContextScope = []string{"canvas"}
readNames := map[string]bool{}
for _, tool := range cloudAgentTools(readOnly) {
readNames[tool["function"].(map[string]any)["name"].(string)] = true
}
for _, name := range []string{"image_text_detect", "image_annotation_render"} {
if !readNames[name] {
t.Fatalf("read-only tool %s missing", name)
}
}
if readNames["image_layer_split"] {
t.Fatal("image_layer_split must not be exposed in read-only mode")
}
write := readOnly
write.PermissionMode = "auto"
write.Budget.MaxGenerationTasks = 1
writeNames := map[string]bool{}
for _, tool := range cloudAgentTools(write) {
writeNames[tool["function"].(map[string]any)["name"].(string)] = true
}
if !writeNames["image_layer_split"] || !cloudAgentWrite("image_layer_split") {
t.Fatal("image_layer_split must be an approved write tool")
}
if got := cloudAgentMediaCall(cloudAgentCall{Function: struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
}{Name: "image_layer_split", Arguments: `{"prompt":"split"}`}}); got.Function.Name != "image_layer_split" || !strings.Contains(got.Function.Arguments, `"mode":"image"`) {
t.Fatalf("image layer split was not normalized to image media: %+v", got.Function)
}
}
func TestCloudAgentAnnotationRenderFeedsControlledTransientReference(t *testing.T) {
s, db, _, _ := creationTestService(t)
canvas := &model.CanvasProject{ID: "annotation-canvas", UserID: "user", PayloadJSON: `{"nodes":[{"id":"image-1","type":"image","title":"原图","width":640,"height":480,"metadata":{"content":"saved"}}],"connections":[]}`}
if err := db.Create(canvas).Error; err != nil {
t.Fatal(err)
}
state := &cloudAgentRuntime{Request: CloudAgentRequest{CanvasID: canvas.ID}, TransientReferences: map[string]cloudAgentTransientReference{}}
call := cloudAgentCall{ID: "call-annotation"}
call.Function.Name = "image_annotation_render"
call.Function.Arguments = `{"nodeId":"image-1","annotations":[{"label":"主体","x":0.5,"y":0.5}]}`
result, err := cloudAgentReadTool(s.repo, "user", state, call)
if err != nil {
t.Fatal(err)
}
view := result.(map[string]any)
refID := stringValue(view["referenceTransientId"])
ref, ok := state.TransientReferences[refID]
if !ok || !strings.HasPrefix(ref.DataURL, "data:image/png;base64,") {
t.Fatalf("annotation transient reference missing or unsafe: %#v", state.TransientReferences)
}
resultJSON, _ := json.Marshal(result)
if !strings.Contains(string(resultJSON), "image/png") {
t.Fatalf("annotation result must advertise PNG reference: %s", resultJSON)
}
doc, err := creationDocument(canvas.PayloadJSON)
if err != nil {
t.Fatal(err)
}
args := cloudAgentMediaArgs{Mode: "image", Prompt: "按标注编辑", SnapshotHash: cloudAgentMediaContentHash(doc), NodeID: "result-image", Title: "编辑结果", Size: "1:1", ReferenceTransientIDs: []string{refID}}
_, _, refs, err := cloudAgentMediaDocument(s.repo, "user", canvas.ID, args, state.TransientReferences)
if err != nil {
t.Fatal(err)
}
images, ok := refs["referenceImages"].([]any)
if !ok || len(images) != 1 || stringValue(images[0].(map[string]any)["id"]) != refID {
t.Fatalf("controlled transient reference was not forwarded: %#v", refs)
}
}
func TestCloudAgentPolicyPublishesSkillManifestWithoutInliningSkillBody(t *testing.T) {
skill := cloudAgentSkill{ID: "skill-1", Name: "任务技能", Version: "v1", Hash: agentProfileHash("skill"), Instruction: "PRIVATE_SKILL_BODY", Files: map[string]string{"references/a.md": "A"}}
text, _, err := compileCloudAgentPolicies(agentTestRequest(), []cloudAgentSkill{skill}, "", cloudAgentProfileSnapshot{Revision: agentProfileRevision(nil), Hash: agentProfileHash("")})