- audio.StemSeparator/StemSeparationProvider: Demucs zip transport via
POST /upstream/<id>/v1/stems (Mode two -> two_stems=vocals; model +
format fields); bounded zip unpack, entry name -> stem, ext -> MIME.
- SFXModel reuses musicgen against the sync /upstream/<id>/v1/sfx route
(JSON prompt/seconds/steps/cfg_scale/seed -> WAV); musicgen.Request
gains CFGScale.
- audio.SpeechEnhancer/SpeechEnhancementProvider:
POST /upstream/<id>/v1/enhance -> WAV (result reuses SpeechResult).
- SpeechRequest.ReferenceAudio/ReferenceMIME (+WithReferenceAudio):
llamaswap switches to the chatterbox clone route
POST /upstream/<id>/v1/audio/speech/upload (input + voice_file),
wav MIME fallback.
- TranscriptionRequest.Translate (+WithTranslate): translate=true form
field, language=auto forced when no explicit hint (whisper.cpp default
en would skip translation).
- httptest contract tests (zip unpack, clone-route switch, translate +
auto-language injection); ADR-0024 (index row deferred — MJ-A backfills
the ADR index table and parallel edits would conflict).
Co-Authored-By: Claude Fable 5 <[email protected]>
- upstreamPath rejects '..' in model ids AND in the rest path — the rest
can embed SERVER-SUPPLIED components (ACE-Step result file URLs), so
dot-dot/scheme smuggling toward other proxy endpoints is refused
- singleImageResult requires positive image evidence (sniffed magic OR
declared image/*): an empty-Content-Type error page can no longer pass
as 'the image' via sniffImageMIME's PNG-default labelling
- upscale/background responses get a dedicated 256MB cap (the 64MB cap
is JSON-sized; a 4x PNG legitimately exceeds it)
- mesh JSON-detection widened (512-byte whitespace-tolerant peek + reject
declared application/json)
- Transcribe now reuses buildMultipart; transcriptionFilename takes
(filename, mime) so diarize shares it without a fake request struct;
truncateForError stops shadowing builtin cap; OnlyMask doc de-ambiguated
Co-Authored-By: Claude Fable 5 <[email protected]>
maxVideoResponseBytes (512MB) replaces the shared 64MB JSON cap on the
/v1/videos/sync read path (doRaw now takes the cap per call) — 3/6
models flagged that a legitimate long/high-bitrate clip would be
discarded after minutes of GPU work. Plus a stale stable-diffusion
comment in initImageFilename and a test-handler early return.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
- videoMIME no longer hard-falls-back to video/mp4: a 2xx body that is
neither declared nor sniffable as video (JSON job envelope, HTML error
page) is now an APIError instead of a 'successful' corrupt clip.
- Resolution rides the wire as width/height AND the OpenAI-style size
string, so either upstream convention honors an explicit request.
- writeFormFields + mimeFromContentType shared helpers replace the
copied multipart loop (audio.go/video.go) and Content-Type branch.
- ADR-0019 indexed in docs/adr/README.md; README gains the videogen
section + support-matrix mention (docs-parity rule).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
- Transcribe: sanitize the caller-supplied multipart filename (CR/LF
would inject Content-Disposition headers; upload metadata is
untrusted), always send the required model/response_format fields,
parse MIME parameters before extension matching, and give audio/opus
its own .opus extension.
- doRaw: a response larger than maxResponseBytes is now an error, not a
silent truncation.
- Shared plumbing: requireBaseURL() + newRequest() helpers replace the
7x-duplicated guard/error string and the triplicated request
building across doJSON/doRaw/Health.
- Health: non-2xx now returns *llm.APIError (package convention,
programmatically distinguishable from transport failure) instead of
a one-off unexported error type.
- Speak: reject negative Speed; speechMIME no longer accepts video/*
Content-Types.
- image.go: Generate/Edit share one sdWire validate+map helper.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
- New leaf package `audio` (ADR-0017): SpeechModel/SpeechProvider and
TranscriptionModel/TranscriptionProvider with imagegen conventions
(zero value = backend default, functional options + Apply, bytes
in/out, never URLs). Root re-exports added.
- imagegen.Editor (ADR-0018): optional image-to-image interface —
EditRequest carries the generation knobs plus Init image and
denoising Strength; separate interface so existing Models keep
compiling.
- provider/llamaswap implements all of it: POST /v1/audio/speech (JSON,
raw-audio response, MIME from Content-Type with format fallback),
POST /v1/audio/transcriptions (multipart, response_format=json),
ListVoices (GET /v1/audio/voices?model=, tolerant of string-list and
object-list shapes), POST /sdapi/v1/img2img (txt2img wire +
init_images/denoising_strength, shared image decode), and Health(ctx)
(GET /health) — a cheap liveness probe for often-offline hosts.
- Hermetic httptest coverage for every new wire shape and validation
path; README sections + support-matrix footnote updated in the same
commit (also corrects the stale /v1/images/generations claim — the
image path has been SDAPI since the seed fix).
First consumer: mort's llamaswap media tool cluster (status / image /
TTS / STT agent tools against the netherstorm host).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj