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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
- Unload: reject model ids containing path separators (/?#) so a model name
can't redirect the request to another endpoint; ":" (common in ids) stays
verbatim.
- doJSON: take a model arg so image/management HTTP errors carry the target id
(was always ""); add a base-URL guard so management methods fail clearly
instead of building a bare-path request; cap the success-path JSON decode with
io.LimitReader (64 MiB) and drain the body when out is nil for conn reuse.
- image: reject negative Request.N before sending.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add provider/llamaswap, a tailored provider for llama-swap (the model-swapping
proxy over llama.cpp / stable-diffusion.cpp). Its chat path delegates to
provider/openai at {base}/v1 — no duplicated wire client (ADR-0007) — with
legacy max_tokens, a Bearer no-key placeholder for keyless local instances, and
a timeout-free client so cold model swaps rely on context deadlines. The
"tailored" surface is concrete management methods (ListModels / Running /
Unload) that don't belong on the canonical llm.Provider interface. The
llama-swap:// DSN scheme builds an http base URL (local-first); a no-URL
built-in errors clearly on use, mirroring foreman.
Add imagegen, a new canonical text-to-image interface separate from llm
(Request/Result/Model/Provider; Image = llm.ImagePart so generated images feed
straight back into chat). First backend is llama-swap via OpenAI
/v1/images/generations (b64_json, bytes-only). Re-exported from the root. v1 is
txt2img only.
Hermetic httptest coverage for chat delegation, management endpoints, image
decode, and scheme wiring. ADR-0015 + ADR-0016, README support matrix +
image-gen section, CLAUDE.md package map, and progress.md updated in the same
commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>