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:
@@ -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))}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user