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:
@@ -42,7 +42,11 @@ type meshModel struct {
|
||||
|
||||
// hunyuanGenerateRequest is the Hunyuan3D api_server /generate shape.
|
||||
// Optional fields are pointers/omitempty so unset values fall back to the
|
||||
// server's defaults (mirrors the sd-server wire structs).
|
||||
// server's defaults (mirrors the sd-server wire structs). Type is sent for
|
||||
// forward-compat but the LIVE server's GenerationRequest has no such field
|
||||
// and always returns GLB (verified 2026-07-14) — the response format is
|
||||
// therefore SNIFFED, and non-GLB output is the caller's conversion problem
|
||||
// (meshgen.Converter / the mediautils shim).
|
||||
type hunyuanGenerateRequest struct {
|
||||
Image string `json:"image"`
|
||||
Type string `json:"type,omitempty"`
|
||||
@@ -123,7 +127,24 @@ func (m *meshModel) Generate(ctx context.Context, req meshgen.Request, opts ...m
|
||||
return nil, &llm.APIError{Provider: m.p.name, Model: m.id,
|
||||
Message: "mesh response is JSON, not mesh bytes: " + truncateForError(raw)}
|
||||
}
|
||||
return &meshgen.Result{Mesh: meshgen.Mesh{Data: raw, Format: format, MIME: mimeType}}, nil
|
||||
// Label the result by what the bytes ARE, not what was requested: the
|
||||
// live server ignores the format field entirely.
|
||||
actualFormat, actualMIME := sniffMeshFormat(raw, format, mimeType)
|
||||
return &meshgen.Result{Mesh: meshgen.Mesh{Data: raw, Format: actualFormat, MIME: actualMIME}}, nil
|
||||
}
|
||||
|
||||
// sniffMeshFormat identifies the mesh container from magic bytes, falling
|
||||
// back to the requested format only when the bytes are ambiguous (binary
|
||||
// STL has no magic).
|
||||
func sniffMeshFormat(raw []byte, requested, requestedMIME string) (string, string) {
|
||||
switch {
|
||||
case len(raw) >= 4 && string(raw[:4]) == "glTF":
|
||||
return "glb", meshFormats["glb"]
|
||||
case len(raw) >= 6 && strings.EqualFold(string(raw[:6]), "solid "):
|
||||
return "stl", meshFormats["stl"]
|
||||
default:
|
||||
return requested, requestedMIME
|
||||
}
|
||||
}
|
||||
|
||||
// truncateForError bounds a payload quoted into an error message.
|
||||
|
||||
Reference in New Issue
Block a user