- imagegen.EditRequest.Mask -> sd-server img2img inpainting (white=repaint) - imagegen.Upscaler + BackgroundRemover, videogen.Interpolator, audio.DiarizationModel: new optional provider-minted surfaces - NEW meshgen leaf package (image->3D, glb/stl/obj) - provider/llamaswap: all five via the /upstream/<model>/<path> passthrough (upstreamPath helper, shared one-file multipart builder); binary success bodies validated (non-image, non-video, JSON-mesh rejection); diarization pins output=json (vtt/srt drop speaker labels) Co-Authored-By: Claude Fable 5 <[email protected]>
62 lines
2.2 KiB
Go
62 lines
2.2 KiB
Go
package llamaswap
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"mime/multipart"
|
|
"strings"
|
|
)
|
|
|
|
// upstreamPath builds a path through llama-swap's generic /upstream/<model>/
|
|
// passthrough, which pins the model (triggering the normal load/swap queue)
|
|
// and forwards the remaining path to the upstream verbatim. This is how the
|
|
// provider reaches upstreams whose native APIs carry no routable `model`
|
|
// field (rembg, mediautils, WhisperX, ACE-Step, ...) without needing a
|
|
// llama-swap route per endpoint (ADR-0020).
|
|
//
|
|
// Why reject rather than escape: same rationale as Unload — model ids
|
|
// legitimately contain ":" but never path-structure characters, and escaping
|
|
// would mask a config error instead of surfacing it.
|
|
func upstreamPath(model, rest string) (string, error) {
|
|
if strings.TrimSpace(model) == "" {
|
|
return "", fmt.Errorf("llama-swap: upstream call requires a model id")
|
|
}
|
|
if strings.ContainsAny(model, "/?#") {
|
|
return "", fmt.Errorf("llama-swap: invalid model id %q for upstream call (contains a path separator)", model)
|
|
}
|
|
if !strings.HasPrefix(rest, "/") {
|
|
rest = "/" + rest
|
|
}
|
|
return "/upstream/" + model + rest, nil
|
|
}
|
|
|
|
// filePart is the single file entry of a media multipart form.
|
|
type filePart struct {
|
|
field string // form field name ("file", "audio_file", ...)
|
|
filename string // already sanitized
|
|
data []byte
|
|
}
|
|
|
|
// buildMultipart assembles a one-file multipart body: the file part first,
|
|
// then the given fields (optional fields skipped when empty, matching
|
|
// writeFormFields). wrap labels errors. Returns the body and its content
|
|
// type.
|
|
func buildMultipart(wrap string, file filePart, fields []formField) (*bytes.Buffer, string, error) {
|
|
var buf bytes.Buffer
|
|
w := multipart.NewWriter(&buf)
|
|
fw, err := w.CreateFormFile(file.field, file.filename)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("llama-swap: %s: %w", wrap, err)
|
|
}
|
|
if _, err := fw.Write(file.data); err != nil {
|
|
return nil, "", fmt.Errorf("llama-swap: %s: %w", wrap, err)
|
|
}
|
|
if err := writeFormFields(w, wrap, fields); err != nil {
|
|
return nil, "", err
|
|
}
|
|
if err := w.Close(); err != nil {
|
|
return nil, "", fmt.Errorf("llama-swap: %s: %w", wrap, err)
|
|
}
|
|
return &buf, w.FormDataContentType(), nil
|
|
}
|