fix: live-API corrections — music result shapes, mesh format honesty, mesh conversion
Gadfly review (reusable) / review (pull_request) Successful in 15s
Adversarial Review (Gadfly) / review (pull_request) Successful in 15s
CI / Tidy (pull_request) Successful in 9m26s
CI / Build & Test (pull_request) Successful in 10m0s

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:
2026-07-14 12:22:06 -04:00
co-authored by Claude Fable 5
parent 89a0b2bdd3
commit fd8d56c3c1
5 changed files with 212 additions and 6 deletions
+32 -4
View File
@@ -72,6 +72,36 @@ type queryItem struct {
Result string `json:"result"`
}
// musicResult is the useful subset of ACE-Step's double-encoded result
// blob.
type musicResult struct {
File string `json:"file"`
}
// parseMusicResult decodes the `result` string, tolerating two live-API
// quirks (observed 2026-07-14): the payload is an ARRAY of result objects
// (not a bare object), and string values can contain RAW control
// characters (a literal newline in timing fields) that strict JSON
// rejects. Control chars can only legally sit inside string values in the
// double-encoded blob, so replacing them with spaces preserves structure.
func parseMusicResult(blob string) (musicResult, bool) {
sanitized := strings.Map(func(r rune) rune {
if r < 0x20 {
return ' '
}
return r
}, blob)
var arr []musicResult
if err := json.Unmarshal([]byte(sanitized), &arr); err == nil && len(arr) > 0 {
return arr[0], true
}
var one musicResult
if err := json.Unmarshal([]byte(sanitized), &one); err == nil {
return one, true
}
return musicResult{}, false
}
// musicFormatMIME resolves the clip MIME from the response Content-Type
// or the requested format, reusing speechMIME's table (one format->MIME
// map for the whole provider). wav32 is ACE-Step-specific: normalize it
@@ -232,10 +262,8 @@ func findQueryItem(raw json.RawMessage, taskID string) (*queryItem, error) {
// fetchResult downloads the finished clip named by the job's result blob.
func (m *musicModel) fetchResult(ctx context.Context, format string, item *queryItem) (*musicgen.Result, error) {
var result struct {
File string `json:"file"`
}
if err := json.Unmarshal([]byte(item.Result), &result); err != nil || result.File == "" {
result, ok := parseMusicResult(item.Result)
if !ok || result.File == "" {
return nil, &llm.APIError{Provider: m.p.name, Model: m.id,
Message: "music result blob missing file URL: " + truncateForError([]byte(item.Result))}
}