fix: live-API corrections — music result shapes, mesh format honesty, mesh conversion
Round 2 from live smokes on netherstorm (2026-07-14): - ACE-Step result blob is an ARRAY of objects and carries RAW control characters inside string values (literal newlines) — strict JSON rejected it. parseMusicResult sanitizes control chars (only legal inside string values in the double-encoded blob) and accepts array or object shapes. Regression test uses the live payload shape. - Hunyuan3D GenerationRequest has NO output-format field (the documented type param is fiction) — it always returns GLB. Results are now labelled by sniffed magic bytes, never by the requested format. - NEW meshgen.Converter/ConverterProvider optional surface + llamaswap impl over the mediautils shim POST /v1/convert_mesh — the STL hop for the printer pipeline. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/majordomo/embeddings"
|
||||
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
|
||||
"gitea.stevedudenhoeffer.com/steve/majordomo/meshgen"
|
||||
"gitea.stevedudenhoeffer.com/steve/majordomo/musicgen"
|
||||
)
|
||||
|
||||
@@ -293,3 +294,75 @@ func TestMusicGenerateRejectsHostileFileURL(t *testing.T) {
|
||||
t.Fatal("dot-dot result file URL accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMusicResultLiveShapes(t *testing.T) {
|
||||
// Live ACE-Step (2026-07-14): result is an ARRAY and carries raw
|
||||
// control characters inside string values.
|
||||
arrayWithCtrl := "[{\"file\": \"/v1/audio?path=x.mp3\", \"prompt\": \"line1\nline2\"}]"
|
||||
res, ok := parseMusicResult(arrayWithCtrl)
|
||||
if !ok || res.File != "/v1/audio?path=x.mp3" {
|
||||
t.Fatalf("array+ctrl: ok=%v res=%+v", ok, res)
|
||||
}
|
||||
// Docs shape (bare object) still parses.
|
||||
res, ok = parseMusicResult(`{"file": "/v1/audio?path=y.mp3"}`)
|
||||
if !ok || res.File != "/v1/audio?path=y.mp3" {
|
||||
t.Fatalf("object: ok=%v res=%+v", ok, res)
|
||||
}
|
||||
if _, ok := parseMusicResult("not json"); ok {
|
||||
t.Fatal("garbage parsed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeshResultSniffsActualFormat(t *testing.T) {
|
||||
// Live Hunyuan3D always returns GLB regardless of the requested
|
||||
// format — the result must be labelled by its magic bytes.
|
||||
glb := append([]byte("glTF"), []byte("\x02\x00\x00\x00rest")...)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
_, _ = w.Write(glb)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client()))
|
||||
mm, _ := p.MeshModel("image3d-hunyuan21")
|
||||
res, err := mm.Generate(context.Background(),
|
||||
meshgen.Request{Image: meshgen.Image{Data: []byte{1}}, Format: "stl"})
|
||||
if err != nil {
|
||||
t.Fatalf("Generate: %v", err)
|
||||
}
|
||||
if res.Mesh.Format != "glb" || res.Mesh.MIME != "model/gltf-binary" {
|
||||
t.Fatalf("mesh labelled %s/%s, want glb (sniffed)", res.Mesh.Format, res.Mesh.MIME)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeshConverter(t *testing.T) {
|
||||
stl := []byte("solid m\nendsolid m\n")
|
||||
var gotTarget, gotPath string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotPath = r.URL.Path
|
||||
if err := r.ParseMultipartForm(1 << 20); err != nil {
|
||||
t.Errorf("multipart: %v", err)
|
||||
}
|
||||
gotTarget = r.FormValue("target")
|
||||
w.Header().Set("Content-Type", "model/stl")
|
||||
_, _ = w.Write(stl)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
p := New(WithBaseURL(srv.URL), WithHTTPClient(srv.Client()))
|
||||
mc, err := p.MeshConverter("mediautils")
|
||||
if err != nil {
|
||||
t.Fatalf("MeshConverter: %v", err)
|
||||
}
|
||||
res, err := mc.Convert(context.Background(),
|
||||
meshgen.Mesh{Data: []byte("glTFxxxx"), Format: "glb"}, "stl")
|
||||
if err != nil {
|
||||
t.Fatalf("Convert: %v", err)
|
||||
}
|
||||
if gotPath != "/upstream/mediautils/v1/convert_mesh" || gotTarget != "stl" {
|
||||
t.Errorf("path/target = %q/%q", gotPath, gotTarget)
|
||||
}
|
||||
if res.Mesh.Format != "stl" {
|
||||
t.Errorf("format = %q", res.Mesh.Format)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user